From 1b930c341db0b8bc46c8793cc2415f67fc4cbfb9 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Mon, 22 Apr 2024 12:25:04 +0200 Subject: [PATCH 001/222] Alerting: Fix max_alerts field handling (#86651) Fix max_alerts field parsing --- .../unified/utils/cloud-alertmanager-notifier-types.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts b/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts index 494e9ef231c..e8100e9aba9 100644 --- a/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts +++ b/public/app/features/alerting/unified/utils/cloud-alertmanager-notifier-types.ts @@ -320,7 +320,11 @@ export const cloudNotifierTypes: Array> = [ 'max_alerts', 'Max alerts', 'The maximum number of alerts to include in a single webhook message. Alerts above this threshold are truncated. When leaving this at its default value of 0, all alerts are included.', - { placeholder: '0', validationRule: '(^\\d+$|^$)' } + { + placeholder: '0', + validationRule: '(^\\d+$|^$)', + setValueAs: (value) => (typeof value === 'string' ? parseInt(value, 10) : 0), + } ), httpConfigOption, ], From 3364df27c2dee07962415ec0eed90dba091134ed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:36:11 +0200 Subject: [PATCH 002/222] Update dependency @grafana/scenes to v4.11.2 (#86671) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 183976ad125..b15f0097f1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4181,8 +4181,8 @@ __metadata: linkType: soft "@grafana/scenes@npm:^4.10.0": - version: 4.10.0 - resolution: "@grafana/scenes@npm:4.10.0" + version: 4.11.2 + resolution: "@grafana/scenes@npm:4.11.2" dependencies: "@grafana/e2e-selectors": "npm:10.3.3" react-grid-layout: "npm:1.3.4" @@ -4196,7 +4196,7 @@ __metadata: "@grafana/ui": ^10.0.3 react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/00c2d5f4606184eac757ab01bbace495c5aacec792de3cf55a66dd4597a3ab1bf2bf97806d9271f18107d738244d6c89579fe3db2f1a52506b64243ba1a9d782 + checksum: 10/6979ddd27c3eb21dc2802e27ad398043b81deb4e99383f07d5dc39055f69220c57b8e09ee1945496d3280695fa99ea63801b27aab8ce7ff90481cfc380795562 languageName: node linkType: hard From 54290f2ac44b976d813624daa68f4c8cd73509d2 Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Mon, 22 Apr 2024 12:36:50 +0200 Subject: [PATCH 003/222] Alerting: Fix TestRouteGetRuleStatuses as much as possible. (#86666) This test has been skipped for a long time, so it doesn't work anymore. I've fixed the test so it works again, but left some tests disabled which were apparently flaky. If we see the other test cases flaking, we'll have to disable it again. Fixes: - Use fake access control for most test cases, and real one for FGAC test cases. - Check that "file" in API responses the full folder path, not folder title. --- pkg/services/ngalert/api/api_prometheus_test.go | 16 +++++++++------- pkg/services/ngalert/tests/fakes/rules.go | 8 +++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go index e08152d7441..86a3c328f82 100644 --- a/pkg/services/ngalert/api/api_prometheus_test.go +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -283,7 +283,7 @@ func withLabels(labels data.Labels) forEachState { } func TestRouteGetRuleStatuses(t *testing.T) { - t.Skip() // TODO: Flaky test: https://github.com/grafana/grafana/issues/69146 + // t.Skip() // TODO: Flaky test: https://github.com/grafana/grafana/issues/69146 timeNow = func() time.Time { return time.Date(2022, 3, 10, 14, 0, 0, 0, time.UTC) } orgID := int64(1) @@ -363,7 +363,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { } } } -`, folder.Title), string(r.Body())) +`, folder.Fullpath), string(r.Body())) }) t.Run("with the inclusion of internal Labels", func(t *testing.T) { @@ -429,7 +429,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { } } } -`, folder.Title), string(r.Body())) +`, folder.Fullpath), string(r.Body())) }) t.Run("with a rule that has multiple queries", func(t *testing.T) { @@ -488,7 +488,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { } } } -`, folder.Title), string(r.Body())) +`, folder.Fullpath), string(r.Body())) }) t.Run("with many rules in a group", func(t *testing.T) { @@ -547,12 +547,11 @@ func TestRouteGetRuleStatuses(t *testing.T) { log: log.NewNopLogger(), manager: fakeAIM, store: ruleStore, - authz: &fakeRuleAccessControlService{}, + authz: accesscontrol.NewRuleService(acimpl.ProvideAccessControl(setting.NewCfg())), } c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: createPermissionsForRules(rules, orgID)}} - //c.SignedInUser.Permissions[1] = createPermissionsForRules(rules) response := api.RouteGetRuleStatuses(c) require.Equal(t, http.StatusOK, response.Status()) result := &apimodels.RuleResponse{} @@ -924,6 +923,8 @@ func TestRouteGetRuleStatuses(t *testing.T) { }) t.Run("test with filters on state", func(t *testing.T) { + t.Skip() // TODO: Flaky test: https://github.com/grafana/grafana/issues/69146 + fakeStore, fakeAIM, api := setupAPI(t) // create two rules in the same Rule Group to keep assertions simple rules := ngmodels.GenerateAlertRules(3, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{ @@ -1255,12 +1256,13 @@ func TestRouteGetRuleStatuses(t *testing.T) { func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, PrometheusSrv) { fakeStore := fakes.NewRuleStore(t) fakeAIM := NewFakeAlertInstanceManager(t) + fakeAuthz := &fakeRuleAccessControlService{} api := PrometheusSrv{ log: log.NewNopLogger(), manager: fakeAIM, store: fakeStore, - authz: accesscontrol.NewRuleService(acimpl.ProvideAccessControl(setting.NewCfg())), + authz: fakeAuthz, } return fakeStore, fakeAIM, api diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index 1714011d0bf..9f8bea0c31e 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -68,10 +68,12 @@ mainloop: } if existing == nil { metrics.MFolderIDsServiceCount.WithLabelValues(metrics.NGAlerts).Inc() + title := "TEST-FOLDER-" + util.GenerateShortUID() folders = append(folders, &folder.Folder{ - ID: rand.Int63(), // nolint:staticcheck - UID: r.NamespaceUID, - Title: "TEST-FOLDER-" + util.GenerateShortUID(), + ID: rand.Int63(), // nolint:staticcheck + UID: r.NamespaceUID, + Title: title, + Fullpath: "fullpath_" + title, }) f.Folders[r.OrgID] = folders } From a10dcf966144e81ee11f701dee36f72ac5b9c9c4 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:47:05 +0200 Subject: [PATCH 004/222] Explore (bugfix): Expanded section state (#86594) Fix --- .../app/features/explore/ContentOutline/ContentOutline.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/app/features/explore/ContentOutline/ContentOutline.tsx b/public/app/features/explore/ContentOutline/ContentOutline.tsx index d11b6b7de15..0c4be514453 100644 --- a/public/app/features/explore/ContentOutline/ContentOutline.tsx +++ b/public/app/features/explore/ContentOutline/ContentOutline.tsx @@ -86,7 +86,7 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement | return childTop && childTop >= offsetTop; }); - if (activeChild) { + if (activeChild && isCollapsible(item)) { setActiveSectionChildId(activeChild.id); setActiveSectionId(item.id); break; @@ -95,6 +95,10 @@ export function ContentOutline({ scroller, panelId }: { scroller: HTMLElement | if (activeItem) { setActiveSectionId(activeItem.id); setActiveSectionChildId(undefined); + setSectionsExpanded((prev) => ({ + ...prev, + [item.id]: false, + })); break; } } From 686c8013c36bca02d36faff569decf56141bcaa6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:01:43 +0100 Subject: [PATCH 005/222] Update dependency @types/diff to v5.2.0 (#86675) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index b15f0097f1f..5c3c92e2355 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9573,9 +9573,9 @@ __metadata: linkType: hard "@types/diff@npm:^5": - version: 5.0.9 - resolution: "@types/diff@npm:5.0.9" - checksum: 10/6924740cb67a49771ea3753ee9b15c676860a6227b2bf0200ed9cef4111ff0f59fec8c51c1170bd30a8c7370b32673b308a9cd2da28525130f842194a822ef42 + version: 5.2.0 + resolution: "@types/diff@npm:5.2.0" + checksum: 10/e1d3e6e9fd9d5386496c8716dd89316288d139cd8159a064f886a079149d05d65289b7b725ce1e333d4e77ce8024e210c6e281e9875a636fc17b4c760c2cf85f languageName: node linkType: hard From 7caa30bc2ece852c0e2d40f762280db82dcde1ed Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Mon, 22 Apr 2024 13:42:11 +0200 Subject: [PATCH 006/222] Alerting: Add plugins extension point to alerting home page (#85725) * Add basic extension point to alerting home page * Remove home page scenes app. Improve plugins styles * Remove unused code * Fix home page rendering when no plugins registered * Add row-based integrations component * Add missing margins * Rollback the Box component changes * Remove unused import --- .../src/types/pluginExtensions.ts | 1 + .../alerting/unified/home/GettingStarted.tsx | 29 +----- .../features/alerting/unified/home/Home.tsx | 96 ++++++++----------- .../unified/home/PluginIntegrations.tsx | 45 +++++++++ 4 files changed, 87 insertions(+), 84 deletions(-) create mode 100644 public/app/features/alerting/unified/home/PluginIntegrations.tsx diff --git a/packages/grafana-data/src/types/pluginExtensions.ts b/packages/grafana-data/src/types/pluginExtensions.ts index e7a26223c34..85a123754f8 100644 --- a/packages/grafana-data/src/types/pluginExtensions.ts +++ b/packages/grafana-data/src/types/pluginExtensions.ts @@ -116,6 +116,7 @@ export type PluginExtensionEventHelpers = { // Extension Points available in core Grafana export enum PluginExtensionPoints { AlertInstanceAction = 'grafana/alerting/instance/action', + AlertingHomePage = 'grafana/alerting/home', CommandPalette = 'grafana/commandpalette/action', DashboardPanelMenu = 'grafana/dashboard/panel/menu', DataSourceConfig = 'grafana/datasources/config', diff --git a/public/app/features/alerting/unified/home/GettingStarted.tsx b/public/app/features/alerting/unified/home/GettingStarted.tsx index 6415801a058..1087fda355c 100644 --- a/public/app/features/alerting/unified/home/GettingStarted.tsx +++ b/public/app/features/alerting/unified/home/GettingStarted.tsx @@ -3,23 +3,8 @@ import React from 'react'; import SVG from 'react-inlinesvg'; import { GrafanaTheme2 } from '@grafana/data'; -import { EmbeddedScene, SceneFlexLayout, SceneFlexItem, SceneReactObject } from '@grafana/scenes'; import { useStyles2, useTheme2, Stack, Text, TextLink } from '@grafana/ui'; -export const getOverviewScene = () => { - return new EmbeddedScene({ - body: new SceneFlexLayout({ - children: [ - new SceneFlexItem({ - body: new SceneReactObject({ - component: GettingStarted, - }), - }), - ], - }), - }); -}; - export default function GettingStarted() { const theme = useTheme2(); const styles = useStyles2(getWelcomePageStyles); @@ -110,9 +95,7 @@ export function WelcomeHeader({ className }: { className?: string }) { const styles = useStyles2(getWelcomeHeaderStyles); return ( -
-
Learn about problems in your systems moments after they occur
- + -
+ ); } const getWelcomeHeaderStyles = (theme: GrafanaTheme2) => ({ - welcomeHeaderWrapper: css({ - color: theme.colors.text.primary, - }), - subtitle: css({ - color: theme.colors.text.secondary, - paddingBottom: theme.spacing(2), - }), ctaContainer: css({ padding: theme.spacing(2), display: 'flex', @@ -195,6 +171,7 @@ function WelcomeCTABox({ title, description, href, hrefText }: WelcomeCTABoxProp const getWelcomeCTAButtonStyles = (theme: GrafanaTheme2) => ({ container: css({ + color: theme.colors.text.primary, flex: 1, minWidth: '240px', display: 'grid', diff --git a/public/app/features/alerting/unified/home/Home.tsx b/public/app/features/alerting/unified/home/Home.tsx index c5656587529..d036a8e9f3a 100644 --- a/public/app/features/alerting/unified/home/Home.tsx +++ b/public/app/features/alerting/unified/home/Home.tsx @@ -1,74 +1,54 @@ import React, { useState } from 'react'; import { config } from '@grafana/runtime'; -import { SceneApp, SceneAppPage } from '@grafana/scenes'; -import { usePageNav } from 'app/core/components/Page/usePageNav'; -import { PluginPageContext, PluginPageContextType } from 'app/features/plugins/components/PluginPageContext'; +import { Box, Stack, Tab, TabContent, TabsBar } from '@grafana/ui'; +import { AlertingPageWrapper } from '../components/AlertingPageWrapper'; import { isLocalDevEnv, isOpenSourceEdition } from '../utils/misc'; -import { getOverviewScene, WelcomeHeader } from './GettingStarted'; +import GettingStarted, { WelcomeHeader } from './GettingStarted'; import { getInsightsScenes } from './Insights'; - -let homeApp: SceneApp | undefined; - -export function getHomeApp(insightsEnabled: boolean) { - if (homeApp) { - return homeApp; - } - - if (insightsEnabled) { - homeApp = new SceneApp({ - pages: [ - new SceneAppPage({ - title: 'Alerting', - subTitle: , - url: '/alerting', - hideFromBreadcrumbs: true, - tabs: [ - new SceneAppPage({ - title: 'Insights', - url: '/alerting/home/insights', - getScene: getInsightsScenes, - }), - new SceneAppPage({ - title: 'Get started', - url: '/alerting/home/overview', - getScene: getOverviewScene, - }), - ], - }), - ], - }); - } else { - homeApp = new SceneApp({ - pages: [ - new SceneAppPage({ - title: 'Alerting', - subTitle: , - url: '/alerting', - hideFromBreadcrumbs: true, - getScene: getOverviewScene, - }), - ], - }); - } - - return homeApp; -} +import { PluginIntegrations } from './PluginIntegrations'; export default function Home() { const insightsEnabled = (!isOpenSourceEdition() || isLocalDevEnv()) && Boolean(config.featureToggles.alertingInsights); - const appScene = getHomeApp(insightsEnabled); - - const sectionNav = usePageNav('alerting')!; - const [pluginContext] = useState({ sectionNav }); + const [activeTab, setActiveTab] = useState<'insights' | 'overview'>(insightsEnabled ? 'insights' : 'overview'); + const insightsScene = getInsightsScenes(); return ( - - - + + + + + + + + {insightsEnabled && ( + setActiveTab('insights')} + /> + )} + setActiveTab('overview')} + /> + + + {activeTab === 'insights' && } + {activeTab === 'overview' && } + + + ); } diff --git a/public/app/features/alerting/unified/home/PluginIntegrations.tsx b/public/app/features/alerting/unified/home/PluginIntegrations.tsx new file mode 100644 index 00000000000..a65b784211b --- /dev/null +++ b/public/app/features/alerting/unified/home/PluginIntegrations.tsx @@ -0,0 +1,45 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { PluginExtensionPoints } from '@grafana/data'; +import { GrafanaTheme2 } from '@grafana/data/'; +import { getPluginComponentExtensions } from '@grafana/runtime'; +import { Stack, Text } from '@grafana/ui'; +import { useStyles2 } from '@grafana/ui/'; + +export function PluginIntegrations() { + const styles = useStyles2(getStyles); + + const { extensions } = getPluginComponentExtensions({ + extensionPointId: PluginExtensionPoints.AlertingHomePage, + limitPerPlugin: 1, + }); + + if (extensions.length === 0) { + return null; + } + + return ( + + + Speed up your alerts creation now by using one of our tailored apps + + + {extensions.map((extension) => ( +
+ +
+ ))} +
+
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + box: css({ + padding: theme.spacing(2), + flex: 1, + backgroundColor: theme.colors.background.secondary, + maxWidth: '460px', + }), +}); From cad9e23e541c0e402f07449f8bb82f0969a8ccc2 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Mon, 22 Apr 2024 14:31:11 +0200 Subject: [PATCH 007/222] Login page: Fix button width (#86680) * Fix login buttons width * Login page: Fix button width * Add todo --- .../components/Login/LoginServiceButtons.tsx | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/public/app/core/components/Login/LoginServiceButtons.tsx b/public/app/core/components/Login/LoginServiceButtons.tsx index a2d67b05749..0bdcba60226 100644 --- a/public/app/core/components/Login/LoginServiceButtons.tsx +++ b/public/app/core/components/Login/LoginServiceButtons.tsx @@ -149,24 +149,27 @@ export const LoginServiceButtons = () => { if (hasServices) { return ( - - - {Object.entries(enabledServices).map(([key, service]) => { - const serviceName = service.name; - return ( - - - Sign in with {{ serviceName }} - - ); - })} - + // TODO: Remove extra div when Stack supports width +
+ + + {Object.entries(enabledServices).map(([key, service]) => { + const serviceName = service.name; + return ( + + + Sign in with {{ serviceName }} + + ); + })} + +
); } From 2247d6c41599d260d24026913d51e4a1ace120a5 Mon Sep 17 00:00:00 2001 From: Kristina Date: Mon, 22 Apr 2024 07:39:24 -0500 Subject: [PATCH 008/222] Short Links: Add setting for changing expiration time (#86003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add setting for changing shortlink expiration time * Add docs, add better language * put all the numbers in the duration 🤷 * 🙄 * update language to be correct and clear * Add max limit and more documentation --- conf/defaults.ini | 5 +++++ docs/sources/explore/_index.md | 2 +- .../sources/setup-grafana/configure-grafana/_index.md | 10 ++++++++++ pkg/services/cleanup/cleanup.go | 2 +- pkg/setting/setting.go | 11 +++++++++++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index e1acf689bb5..b3a459c7df1 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1422,6 +1422,11 @@ concurrent_query_limit = # Enable the Query history enabled = true +#################################### Short Links ############################# +[short_links] +# Short links which are never accessed will be deleted as cleanup. Time is in days. Default is 7 days. Max is 365. 0 means they will be deleted approximately every 10 minutes. +expire_time = 7 + #################################### Internal Grafana Metrics ############ # Metrics available at HTTP URL /metrics and /metrics/plugins/:pluginId [metrics] diff --git a/docs/sources/explore/_index.md b/docs/sources/explore/_index.md index 640c9785aa2..2d9a88253dc 100644 --- a/docs/sources/explore/_index.md +++ b/docs/sources/explore/_index.md @@ -137,7 +137,7 @@ Available in Grafana 7.3 and later versions. The Share shortened link capability allows you to create smaller and simpler URLs of the format /goto/:uid instead of using longer URLs with query parameters. To create a shortened link to the executed query, click the **Share** option in the Explore toolbar. -A shortened link will automatically get deleted after seven (7) days from its creation if it's never used. If a link is used at least once, it won't ever get deleted. +A shortened link that is not accessed will automatically get deleted after a [configurable period](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/#short_links) (defaulting to seven days). If a link is used at least once, it won't be deleted. ### Sharing shortened links with absolute time diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 114f1ae6bd7..0cbd5fa157a 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -1779,6 +1779,16 @@ Enable or disable the Query history. Default is `enabled`.
+## [short_links] + +Configures settings around the short link feature. + +### expire_time + +Short links which are never accessed are considered expired or stale, and will be deleted as cleanup. Set the expiration time in days. Default is `7` days. Maximum is `365` days, and setting above the maximum will have `365` set instead. Setting `0` means the short links will be cleaned up approximately every 10 minutes. + +
+ ## [metrics] For detailed instructions, refer to [Internal Grafana metrics]({{< relref "../set-up-grafana-monitoring" >}}). diff --git a/pkg/services/cleanup/cleanup.go b/pkg/services/cleanup/cleanup.go index 3b1a2c5e201..46037db3b4e 100644 --- a/pkg/services/cleanup/cleanup.go +++ b/pkg/services/cleanup/cleanup.go @@ -257,7 +257,7 @@ func (srv *CleanUpService) expireOldVerifications(ctx context.Context) { func (srv *CleanUpService) deleteStaleShortURLs(ctx context.Context) { logger := srv.log.FromContext(ctx) cmd := shorturls.DeleteShortUrlCommand{ - OlderThan: time.Now().Add(-time.Hour * 24 * 7), + OlderThan: time.Now().Add(-time.Duration(srv.Cfg.ShortLinkExpiration*24) * time.Hour), } if err := srv.ShortURLService.DeleteStaleShortURLs(ctx, &cmd); err != nil { logger.Error("Problem deleting stale short urls", "error", err.Error()) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index ed864575f6d..478d28afdcf 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -516,6 +516,9 @@ type Cfg struct { // Experimental scope settings ScopesListScopesURL string ScopesListDashboardsURL string + + //Short Links + ShortLinkExpiration int } // AddChangePasswordLink returns if login form is disabled or not since @@ -1158,6 +1161,14 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error { queryHistory := iniFile.Section("query_history") cfg.QueryHistoryEnabled = queryHistory.Key("enabled").MustBool(true) + shortLinks := iniFile.Section("short_links") + cfg.ShortLinkExpiration = shortLinks.Key("expire_time").MustInt(7) + + if cfg.ShortLinkExpiration > 365 { + cfg.Logger.Warn("short_links expire_time must be less than 366 days. Setting to 365 days") + cfg.ShortLinkExpiration = 365 + } + panelsSection := iniFile.Section("panels") cfg.DisableSanitizeHtml = panelsSection.Key("disable_sanitize_html").MustBool(false) From 12771e49fc13b4b6e7e073100c4f7d0f91d915a5 Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Mon, 22 Apr 2024 15:02:40 +0200 Subject: [PATCH 009/222] Dashboards: Check if dashboard.meta is undefined, if undefined handle redirect in dashboard scene. (#86674) dashboard meta is not available if the dashboard response is a redirect --- public/app/features/dashboard/containers/DashboardPageProxy.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.tsx index b4182e9f7ca..c31f05630b4 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.tsx @@ -60,7 +60,7 @@ function DashboardPageProxy(props: DashboardPageProxyProps) { if ( dashboard.value && - !(dashboard.value.meta.canEdit || dashboard.value.meta.canMakeEditable) && + !(dashboard.value.meta?.canEdit || dashboard.value.meta?.canMakeEditable) && isScenesSupportedRoute ) { return ; From 7564d5cee705feda57cacd1171f264249216989f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:18:57 +0100 Subject: [PATCH 010/222] Update dependency core-js to v3.37.0 (#86676) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 5d68c993123..834bcd9dc7c 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "chrome-remote-interface": "0.33.0", "codeowners": "^5.1.1", "copy-webpack-plugin": "12.0.2", - "core-js": "3.36.1", + "core-js": "3.37.0", "css-loader": "6.10.0", "css-minimizer-webpack-plugin": "6.0.0", "cypress": "13.1.0", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 8601c4ce5a9..0201c430c7d 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -158,7 +158,7 @@ "@types/tinycolor2": "1.4.6", "@types/uuid": "9.0.8", "common-tags": "1.8.2", - "core-js": "3.36.1", + "core-js": "3.37.0", "css-loader": "6.10.0", "csstype": "3.1.3", "esbuild": "0.18.12", diff --git a/yarn.lock b/yarn.lock index 5c3c92e2355..6e0d6c63c30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4342,7 +4342,7 @@ __metadata: calculate-size: "npm:1.1.1" classnames: "npm:2.5.1" common-tags: "npm:1.8.2" - core-js: "npm:3.36.1" + core-js: "npm:3.37.0" css-loader: "npm:6.10.0" csstype: "npm:3.1.3" d3: "npm:7.9.0" @@ -14023,10 +14023,10 @@ __metadata: languageName: node linkType: hard -"core-js@npm:3.36.1, core-js@npm:^3.6.0, core-js@npm:^3.8.3": - version: 3.36.1 - resolution: "core-js@npm:3.36.1" - checksum: 10/ce1e1bfc1034b6f2ff7c91077319e8abdd650ee606ffe6e80073e64ab9d8aad2d6a6d953461b01f331a6f796ad2fd766a3386b88aa371b45d44fa7c0b9913ce6 +"core-js@npm:3.37.0, core-js@npm:^3.6.0, core-js@npm:^3.8.3": + version: 3.37.0 + resolution: "core-js@npm:3.37.0" + checksum: 10/97feac0b54b95d928bda6a6e611cf34963a265a5fe8ab46ed35bbc9d32a14221bf6bede5d6cd4b0c0f30e8440cf1eff0c4f0c242d719c561e5dd73d3b005d63c languageName: node linkType: hard @@ -18759,7 +18759,7 @@ __metadata: comlink: "npm:4.4.1" common-tags: "npm:1.8.2" copy-webpack-plugin: "npm:12.0.2" - core-js: "npm:3.36.1" + core-js: "npm:3.37.0" css-loader: "npm:6.10.0" css-minimizer-webpack-plugin: "npm:6.0.0" cypress: "npm:13.1.0" From 6ab9dcde8d8fd3cfd3414beed4e988255fe45307 Mon Sep 17 00:00:00 2001 From: Misi Date: Mon, 22 Apr 2024 15:34:16 +0200 Subject: [PATCH 011/222] LDAP: Fix listing all non-matching groups (#86682) Fix getRowId in LdapUserGroups to list all non-matching groups --- public/app/features/admin/ldap/LdapUserGroups.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/admin/ldap/LdapUserGroups.tsx b/public/app/features/admin/ldap/LdapUserGroups.tsx index d8a628acf95..ced2acdab68 100644 --- a/public/app/features/admin/ldap/LdapUserGroups.tsx +++ b/public/app/features/admin/ldap/LdapUserGroups.tsx @@ -46,7 +46,7 @@ export const LdapUserGroups = ({ groups }: Props) => { }} columns={columns} data={items} - getRowId={(row) => row.orgId + row.orgRole} + getRowId={(row) => row.orgId + row.orgRole + row.groupDN} /> ); }; From 427f361f5829a1e2576f6857b94c97002748a684 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 13:21:34 +0000 Subject: [PATCH 012/222] Update dependency @grafana/scenes to v4.11.3 --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6e0d6c63c30..846a6bda9c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4181,8 +4181,8 @@ __metadata: linkType: soft "@grafana/scenes@npm:^4.10.0": - version: 4.11.2 - resolution: "@grafana/scenes@npm:4.11.2" + version: 4.11.3 + resolution: "@grafana/scenes@npm:4.11.3" dependencies: "@grafana/e2e-selectors": "npm:10.3.3" react-grid-layout: "npm:1.3.4" @@ -4196,7 +4196,7 @@ __metadata: "@grafana/ui": ^10.0.3 react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/6979ddd27c3eb21dc2802e27ad398043b81deb4e99383f07d5dc39055f69220c57b8e09ee1945496d3280695fa99ea63801b27aab8ce7ff90481cfc380795562 + checksum: 10/a86f60be983f575853ce1a444b798671a069ee4b116f0f2c164c56651bb197ffbe511c39d5739aaa246ddeb7d576f8daceb73f0fbcfd8c809ff6b5c352c651b1 languageName: node linkType: hard From 14f018e3fc3dd7a7ffba68cd7ee1db59f8939647 Mon Sep 17 00:00:00 2001 From: Julian Siebert Date: Mon, 22 Apr 2024 15:53:18 +0200 Subject: [PATCH 013/222] Docs: Use correct description for "og_priority" (#80889) --- .../ngalert/notifier/channels_config/available_channels.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index bf6d5811c32..aaf2a938c83 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -1281,7 +1281,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { }, { Label: "Override priority", Element: ElementTypeCheckbox, - Description: "Allow the alert priority to be set using the og_priority annotation", + Description: "Allow the alert priority to be set using the og_priority label.", PropertyName: "overridePriority", }, { From 50b285ac69659aca8651674dad30036d2a5f52fd Mon Sep 17 00:00:00 2001 From: Marie Cruz Date: Mon, 22 Apr 2024 15:20:10 +0100 Subject: [PATCH 014/222] docs: update candlestick visualization (#86053) * docs: update candlestick visualization * fix: linting issues * Apply suggestions from code review Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * docs: add candlestick video --------- Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> --- .../visualizations/candlestick/index.md | 59 +++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/docs/sources/panels-visualizations/visualizations/candlestick/index.md b/docs/sources/panels-visualizations/visualizations/candlestick/index.md index 770ac1368d5..29714790a88 100644 --- a/docs/sources/panels-visualizations/visualizations/candlestick/index.md +++ b/docs/sources/panels-visualizations/visualizations/candlestick/index.md @@ -21,12 +21,55 @@ weight: 100 # Candlestick -The candlestick visualization allows you to visualize data that includes a number of consistent dimensions focused on price movement. The candlestick visualization includes an Open-High-Low-Close (OHLC) mode, as well as support for additional dimensions based on time series data. - -{{< figure src="/static/img/docs/candlestick-panel/candlestick-panel-8-3.png" max-width="1200px" caption="Candlestick visualization" >}} +The candlestick visualization allows you to visualize data that includes a number of consistent dimensions focused on price movements, such as stock prices. The candlestick visualization includes an [Open-High-Low-Close (OHLC) mode](#open-high-low-close), as well as support for additional dimensions based on time series data. Candlestick visualizations build upon the foundation of the [time series visualization][] and include many common configuration settings. +You can use a candlestick if you want to visualize, at a glance, how a price moved over time, whether it went up, down, or stayed the same, and how much it fluctuated: + +{{< figure src="/static/img/docs/candlestick-panel/candlestick-panel-8-3.png" max-width="1065px" alt="A candlestick visualization" >}} + +Each candlestick is represented as a rectangle, referred to as the _candlestick body_. The candlestick body displays the opening and closing prices during a time period. Green candlesticks represent when the price appreciated while the red candlesticks represent when the price depreciated. The lines sticking out the candlestick body are referred to as _wicks_ or _shadows_, which represent the highest and lowest prices during the time period. + +Use a candlestick when you need to: + +- Monitor and identify trends in price movements of specific assets such as stocks, currencies, or commodities. +- Analyze any volatility in the stock market. +- Provide data analysis to help with trading decisions. + +## Configure a candlestick + +Once you’ve created a [dashboard](https://grafana.com/docs/grafana//dashboards/build-dashboards/create-dashboard/), the following video shows you how to configure a candlestick visualization: + +{{< youtube id="IOFKBgbf3aM" >}} + +{{< docs/play title="Candlestick" url="https://play.grafana.org/d/candlestick/candlestick" >}} + +## Supported data formats + +The candlestick visualization works best with price movement data for an asset. The data must include: + +- **Timestamps** - The time at which each price movement occurred. +- **Opening price** - The price of the asset at the beginning of the time period. +- **Closing price** - The price of the asset at the end of the time period. +- **Highest price** - The highest price the asset reached during the time period. +- **Lowest price** - The lowest price the asset reached during the time period. + +### Example + +| Timestamps | Open | High | Low | Close | +| ------------------- | ----- | ----- | ----- | ----- | +| 2024-03-13 10:05:00 | 0.200 | 0.205 | 0.201 | 0.203 | +| 2024-03-14 10:10:10 | 0.204 | 0.205 | 0.201 | 0.200 | +| 2024-03-15 10:15:10 | 0.204 | 0.205 | 0.201 | 0.200 | +| 2024-03-16 10:20:11 | 0.203 | 0.203 | 0.202 | 0.203 | +| 2024-03-17 10:25:11 | 0.203 | 0.203 | 0.202 | 0.203 | +| 2024-03-18 10:30:12 | 0.202 | 0.202 | 0.201 | 0.201 | + +The data is converted as follows: + +{{< figure src="/static/img/docs/candlestick-panel/candlestick.png" max-width="1065px" alt="A candlestick visualization showing the price movements of specific asset." >}} + ## Mode The mode options allow you to toggle which dimensions are used for the visualization. @@ -63,21 +106,13 @@ The candlestick visualization will attempt to map fields from your data to the a The candlestick visualization legend doesn't display these values. {{% /admonition %}} -To properly map these dimensions, the query results table from your data must include _at least_ the following columns: - -- timestamp -- open -- high -- low -- close - If your data can't be mapped to these dimensions for some reason (for example, because the column names aren't the same), you can map them manually using the **Open**, **High**, **Low**, and **Close** fields under the **Candlestick** options in the panel editor: ![Open, High, Low, and Close fields in the panel editor](/media/docs/grafana/panels-visualizations/screenshot-olhc-options-10.3.png) ## Additional fields -The candlestick visualization is based on the time series visualization. It can visualize additional data dimensions beyond open, high, low, close, and volume The **Include** and **Ignore** options allow it to visualize other included data such as simple moving averages, Bollinger bands and more, using the same styles and configurations available in the [time series][] visualization. +The candlestick visualization is based on the time series visualization. It can visualize additional data dimensions beyond open, high, low, close, and volume The **Include** and **Ignore** options allow it to visualize other included data such as simple moving averages, Bollinger bands and more, using the same styles and configurations available in the [time series][time series visualization] visualization. {{% docs/reference %}} [time series visualization]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/visualizations/time-series" From c47b4ff8c3dd76da17c450c9ddca481174157fc2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 16:04:44 +0100 Subject: [PATCH 015/222] Update dependency mini-css-extract-plugin to v2.9.0 (#86691) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 834bcd9dc7c..44820a165a0 100644 --- a/package.json +++ b/package.json @@ -191,7 +191,7 @@ "jest-matcher-utils": "29.7.0", "jest-watch-typeahead": "^2.2.2", "lerna": "8.1.2", - "mini-css-extract-plugin": "2.8.1", + "mini-css-extract-plugin": "2.9.0", "msw": "2.2.14", "mutationobserver-shim": "0.3.7", "ngtemplate-loader": "2.1.0", diff --git a/yarn.lock b/yarn.lock index 846a6bda9c4..d0c415bb1c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18829,7 +18829,7 @@ __metadata: marked: "npm:12.0.2" marked-mangle: "npm:1.1.7" memoize-one: "npm:6.0.0" - mini-css-extract-plugin: "npm:2.8.1" + mini-css-extract-plugin: "npm:2.9.0" ml-regression-polynomial: "npm:^3.0.0" ml-regression-simple-linear: "npm:^3.0.0" moment: "npm:2.30.1" @@ -22837,15 +22837,15 @@ __metadata: languageName: node linkType: hard -"mini-css-extract-plugin@npm:2.8.1": - version: 2.8.1 - resolution: "mini-css-extract-plugin@npm:2.8.1" +"mini-css-extract-plugin@npm:2.9.0": + version: 2.9.0 + resolution: "mini-css-extract-plugin@npm:2.9.0" dependencies: schema-utils: "npm:^4.0.0" tapable: "npm:^2.2.1" peerDependencies: webpack: ^5.0.0 - checksum: 10/e00f6d19ad1be94701db8e5f126bdf8a9f4739cd8e8eb68690254aac4699c49c872a1ca761461d7d0c37a933f823df5f87674688fe0d568e00e7c0e9d6e5c798 + checksum: 10/4c9ee9c0c6160a64a4884d5a92a1a5c0b68d556cd00f975cf6c8a79b51ac90e6130a37b3832b17d377d0cb1b31c0313c8c023458d4f69e95fe3424a8b43d834f languageName: node linkType: hard From 224d61746a788820c9c2c81680129d40694b4fab Mon Sep 17 00:00:00 2001 From: linoman <2051016+linoman@users.noreply.github.com> Date: Mon, 22 Apr 2024 17:27:58 +0200 Subject: [PATCH 016/222] Remove SAML form the list of auth providers for devenv (#86684) Update README.md The link for SAML was broken. Since SAML is an enterprise feature, it has been removed. --- devenv/docker/blocks/auth/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/devenv/docker/blocks/auth/README.md b/devenv/docker/blocks/auth/README.md index 2046804fb9a..7ebd69161fd 100644 --- a/devenv/docker/blocks/auth/README.md +++ b/devenv/docker/blocks/auth/README.md @@ -28,4 +28,3 @@ by the `devenv` target. - [openldap](./openldap) - [openldap-multiple](./openldap-multiple) - [prometheus_basic_auth_proxy](./prometheus_basic_auth_proxy) -- [saml](./saml) From fc45b56d9dc64eaa7953f0a017fab322b2ae6207 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Mon, 22 Apr 2024 17:01:24 +0100 Subject: [PATCH 017/222] EmptyState: Apply `call-to-action` variant in core (#86448) * apply empty state in a bunch of places * fix unit tests * put alert back on top * add data-testids so e2e tests keep working * remove info boxes * fix annotations empty state alignment with new maxWidth --- .betterer.results | 8 +- .../components/silences/NoSilencesCTA.tsx | 17 +++-- .../components/BrowseView.test.tsx | 2 +- .../components/BrowseView.tsx | 40 +++++++--- .../components/EmptyCorrelationsCTA.tsx | 24 +++--- .../AnnotationSettingsList.test.tsx | 8 +- .../annotations/AnnotationSettingsList.tsx | 59 +++++++++------ .../settings/links/DashboardLinkList.tsx | 50 +++++++++---- .../settings/variables/VariableEditorList.tsx | 60 ++++++++------- .../AnnotationSettingsList.tsx | 59 +++++++++------ .../AnnotationsSettings.test.tsx | 11 +-- .../DashboardSettings/LinksSettings.test.tsx | 9 +-- .../components/DataSourcesList.tsx | 35 +++++---- .../LibraryPanelsSearch.test.tsx | 12 +-- .../LibraryPanelsView/LibraryPanelsView.tsx | 24 +++++- .../PublicDashboardListTable.test.tsx | 4 +- .../PublicDashboardListTable.tsx | 62 +++++++++++----- .../components/SnapshotListTable.tsx | 25 ++++++- public/app/features/playlist/PlaylistPage.tsx | 36 ++++----- .../ServiceAccountsListPage.tsx | 34 +++++---- public/app/features/teams/TeamList.tsx | 38 ++++++---- .../variables/editor/VariableEditorList.tsx | 60 ++++++++------- public/locales/en-US/grafana.json | 74 +++++++++++++++++-- public/locales/pseudo-LOCALE/grafana.json | 74 +++++++++++++++++-- 24 files changed, 554 insertions(+), 271 deletions(-) diff --git a/.betterer.results b/.betterer.results index 89141673931..cdf32535173 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2513,9 +2513,6 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], - "public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/dashboard-scene/settings/annotations/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsEdit\`)", "0"], [0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsList\`)", "1"] @@ -2565,8 +2562,7 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "5"] ], "public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] + [0, 0, 0, "Styles should be written using objects.", "0"] ], "public/app/features/dashboard/components/AnnotationSettings/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsEdit\`)", "0"], @@ -3923,7 +3919,7 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "1"] ], "public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "0"], + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], [0, 0, 0, "Styles should be written using objects.", "2"], [0, 0, 0, "Styles should be written using objects.", "3"], diff --git a/public/app/features/alerting/unified/components/silences/NoSilencesCTA.tsx b/public/app/features/alerting/unified/components/silences/NoSilencesCTA.tsx index b754f5b4fdb..fd42325863a 100644 --- a/public/app/features/alerting/unified/components/silences/NoSilencesCTA.tsx +++ b/public/app/features/alerting/unified/components/silences/NoSilencesCTA.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { CallToActionCard } from '@grafana/ui'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { CallToActionCard, EmptyState, LinkButton } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; import { contextSrv } from 'app/core/services/context_srv'; import { getInstancesPermissions } from '../../utils/access-control'; @@ -16,11 +16,14 @@ export const NoSilencesSplash = ({ alertManagerSourceName }: Props) => { if (contextSrv.hasPermission(permissions.create)) { return ( - + Create silence + + } + message={t('silences.empty-state.title', "You haven't created any silences yet")} /> ); } diff --git a/public/app/features/browse-dashboards/components/BrowseView.test.tsx b/public/app/features/browse-dashboards/components/BrowseView.test.tsx index bdca23a807d..a083f8374c5 100644 --- a/public/app/features/browse-dashboards/components/BrowseView.test.tsx +++ b/public/app/features/browse-dashboards/components/BrowseView.test.tsx @@ -149,7 +149,7 @@ describe('browse-dashboards BrowseView', () => { describe('when there is no item in the folder', () => { it('shows a CTA for creating a dashboard if the user has editor rights', async () => { render(); - expect(await screen.findByText('Create Dashboard')).toBeInTheDocument(); + expect(await screen.findByText('Create dashboard')).toBeInTheDocument(); }); it('shows a simple message if the user has viewer rights', async () => { diff --git a/public/app/features/browse-dashboards/components/BrowseView.tsx b/public/app/features/browse-dashboards/components/BrowseView.tsx index 4c657da02db..b9cc1ea5a2d 100644 --- a/public/app/features/browse-dashboards/components/BrowseView.tsx +++ b/public/app/features/browse-dashboards/components/BrowseView.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from 'react'; -import { CallToActionCard } from '@grafana/ui'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { CallToActionCard, EmptyState, LinkButton, TextLink } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; import { DashboardViewItem } from 'app/features/search/types'; import { useDispatch } from 'app/types'; @@ -117,16 +117,32 @@ export function BrowseView({ folderUID, width, height, canSelect }: BrowseViewPr return (
{canSelect ? ( - '} - proTipLink={folderUID && 'dashboards'} - proTipLinkTitle={folderUID && 'Browse dashboards'} - proTipTarget="" - /> + + Create dashboard + + } + message={ + folderUID + ? t('browse-dashboards.empty-state.title-folder', "This folder doesn't have any dashboards yet") + : t('browse-dashboards.empty-state.title', "You haven't created any dashboards yet") + } + > + {folderUID && ( + + Add/move dashboards to your folder at{' '} + + Browse dashboards + + + )} + ) : ( This folder is empty} /> )} diff --git a/public/app/features/correlations/components/EmptyCorrelationsCTA.tsx b/public/app/features/correlations/components/EmptyCorrelationsCTA.tsx index 1228224ffc2..b4f020295ce 100644 --- a/public/app/features/correlations/components/EmptyCorrelationsCTA.tsx +++ b/public/app/features/correlations/components/EmptyCorrelationsCTA.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { Card } from '@grafana/ui'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { Button, Card, EmptyState } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; interface Props { onClick?: () => void; @@ -11,13 +11,19 @@ export const EmptyCorrelationsCTA = ({ onClick, canWriteCorrelations }: Props) = // TODO: if there are no datasources show a different message return canWriteCorrelations ? ( - + + Add correlation + + } + message={t('correlations.empty-state.title', "You haven't defined any correlations yet")} + > + + You can also define correlations via datasource provisioning + + ) : ( There are no correlations configured yet. diff --git a/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.test.tsx b/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.test.tsx index 3a9d6fc28bf..ab7a07b7393 100644 --- a/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.test.tsx +++ b/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.test.tsx @@ -65,21 +65,21 @@ describe('AnnotationSettingsEdit', () => { it('should render with empty list message', async () => { const { - renderer: { getByTestId }, + renderer: { getByRole }, } = await setup(true); - const emptyListBtn = getByTestId(selectors.components.CallToActionCard.buttonV2(BUTTON_TITLE)); + const emptyListBtn = getByRole('button', { name: BUTTON_TITLE }); expect(emptyListBtn).toBeInTheDocument(); }); it('should create new annotation when empty list button is pressed', async () => { const { - renderer: { getByTestId }, + renderer: { getByRole }, user, } = await setup(true); - const emptyListBtn = getByTestId(selectors.components.CallToActionCard.buttonV2(BUTTON_TITLE)); + const emptyListBtn = getByRole('button', { name: BUTTON_TITLE }); await user.click(emptyListBtn); diff --git a/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.tsx b/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.tsx index c36d12510d6..154851834e2 100644 --- a/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.tsx +++ b/public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsList.tsx @@ -4,8 +4,8 @@ import React from 'react'; import { AnnotationQuery } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { getDataSourceSrv } from '@grafana/runtime'; -import { Button, DeleteButton, IconButton, useStyles2, VerticalGroup } from '@grafana/ui'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { Button, DeleteButton, EmptyState, IconButton, Stack, TextLink, useStyles2 } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; import { ListNewButton } from 'app/features/dashboard/components/DashboardSettings/ListNewButton'; import { MoveDirection } from '../AnnotationsEditView'; @@ -39,7 +39,7 @@ export const AnnotationSettingsList = ({ annotations, onNew, onEdit, onMove, onD const dataSourceSrv = getDataSourceSrv(); return ( - + {annotations.length > 0 && (
@@ -99,25 +99,38 @@ export const AnnotationSettingsList = ({ annotations, onNew, onEdit, onMove, onD )} {showEmptyListCTA && ( - Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines - and icons on all graph panels. When you hover over an annotation icon you can get event text & tags for - the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or - drag region). These will be stored in Grafana's annotation database. -

- Checkout the - Annotations documentation - for more information.`, - }} - /> + + + Add annotation query + + } + message={t('annotations.empty-state.title', 'There are no custom annotation queries added yet')} + > + +

+ Annotations provide a way to integrate event data into your graphs. They are visualized as vertical + lines and icons on all graph panels. When you hover over an annotation icon you can get event text & + tags for the event. You can add annotation events directly from grafana by holding CTRL or CMD + click + on graph (or drag region). These will be stored in Grafana's annotation database. +

+
+ + Checkout the{' '} + + Annotations documentation + {' '} + for more information. + +
+
)} {!showEmptyListCTA && ( )} - + ); }; diff --git a/public/app/features/dashboard-scene/settings/links/DashboardLinkList.tsx b/public/app/features/dashboard-scene/settings/links/DashboardLinkList.tsx index 9e9f76d3d15..d235eb7f7b7 100644 --- a/public/app/features/dashboard-scene/settings/links/DashboardLinkList.tsx +++ b/public/app/features/dashboard-scene/settings/links/DashboardLinkList.tsx @@ -3,8 +3,19 @@ import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { DashboardLink } from '@grafana/schema'; -import { Button, DeleteButton, HorizontalGroup, Icon, IconButton, TagList, useStyles2 } from '@grafana/ui'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { + Button, + DeleteButton, + EmptyState, + HorizontalGroup, + Icon, + IconButton, + Stack, + TagList, + TextLink, + useStyles2, +} from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; interface DashboardLinkListProps { links: DashboardLink[]; @@ -28,19 +39,28 @@ export function DashboardLinkList({ if (isEmptyList) { return ( -
- Dashboard Links allow you to place links to other dashboards and web sites directly below the dashboard header.

', - }} - /> -
+ + + Add dashboard link + + } + message={t('dashboard-links.empty-state.title', 'There are no dashboard links added yet')} + > + + Dashboard links allow you to place links to other dashboards and web sites directly below the dashboard + header.{' '} + + Learn more + + + + ); } diff --git a/public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx b/public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx index b844531febb..f04e8e7b9ea 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx @@ -5,8 +5,8 @@ import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; import { selectors } from '@grafana/e2e-selectors'; import { reportInteraction } from '@grafana/runtime'; import { SceneVariable, SceneVariableState } from '@grafana/scenes'; -import { useStyles2, Stack, Button } from '@grafana/ui'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { useStyles2, Stack, Button, EmptyState, TextLink } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; import { VariableEditorListRow } from './VariableEditorListRow'; @@ -98,30 +98,38 @@ export function VariableEditorList({ function EmptyVariablesList({ onAdd }: { onAdd: () => void }): ReactElement { return ( -
- - Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server - or sensor names in your metric queries you can use variables in their place. Variables are shown as - list boxes at the top of the dashboard. These drop-down lists make it easy to change the data - being displayed in your dashboard. Check out the - - Templates and variables documentation - - for more information. -

`, - }} - infoBoxTitle="What do variables do?" - onClick={(event) => { - event.preventDefault(); - onAdd(); - }} - /> -
+ + + Add variable + + } + message={t('variables.empty-state.title', 'There are no variables added yet')} + > +

+ + Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server or + sensor names in your metric queries you can use variables in their place. Variables are shown as list boxes + at the top of the dashboard. These drop-down lists make it easy to change the data being displayed in your + dashboard. + +

+ + Check out the{' '} + + Templates and variables documentation + {' '} + for more information. + +
+
); } diff --git a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx index 10b5e49de3d..6a5579ee843 100644 --- a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx +++ b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx @@ -4,8 +4,8 @@ import React, { useState } from 'react'; import { arrayUtils, AnnotationQuery } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { getDataSourceSrv } from '@grafana/runtime'; -import { Button, DeleteButton, IconButton, useStyles2, VerticalGroup } from '@grafana/ui'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { Button, DeleteButton, EmptyState, IconButton, Stack, TextLink, useStyles2 } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; import { DashboardModel } from '../../state/DashboardModel'; import { ListNewButton } from '../DashboardSettings/ListNewButton'; @@ -54,7 +54,7 @@ export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => { const dataSourceSrv = getDataSourceSrv(); return ( - + {annotations.length > 0 && (
@@ -108,25 +108,38 @@ export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => { )} {showEmptyListCTA && ( - Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines - and icons on all graph panels. When you hover over an annotation icon you can get event text & tags for - the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or - drag region). These will be stored in Grafana's annotation database. -

- Checkout the - Annotations documentation - for more information.`, - }} - /> + + + Add annotation query + + } + message={t('annotations.empty-state.title', 'There are no custom annotation queries added yet')} + > + +

+ Annotations provide a way to integrate event data into your graphs. They are visualized as vertical + lines and icons on all graph panels. When you hover over an annotation icon you can get event text & + tags for the event. You can add annotation events directly from grafana by holding CTRL or CMD + click + on graph (or drag region). These will be stored in Grafana's annotation database. +

+
+ + Checkout the{' '} + + Annotations documentation + {' '} + for more information. + +
+
)} {!showEmptyListCTA && ( { New query )} - + ); }; diff --git a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx index a5f0962c799..f90472ac6e8 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx @@ -3,7 +3,6 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import { TestProvider } from 'test/helpers/TestProvider'; -import { selectors } from '@grafana/e2e-selectors'; import { locationService, setAngularLoader, setDataSourceSrv } from '@grafana/runtime'; import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified/mocks'; @@ -89,9 +88,7 @@ describe('AnnotationsSettings', () => { expect(screen.queryByRole('grid')).toBeInTheDocument(); expect(screen.getByRole('row', { name: /annotations & alerts \(built-in\) -- grafana --/i })).toBeInTheDocument(); - expect( - screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add annotation query')) - ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Add annotation query' })).toBeInTheDocument(); expect(screen.queryByRole('link', { name: /annotations documentation/i })).toBeInTheDocument(); }); @@ -99,9 +96,7 @@ describe('AnnotationsSettings', () => { dashboard.annotations.list = []; setup(dashboard); - expect( - screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add annotation query')) - ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Add annotation query' })).toBeInTheDocument(); }); test('it renders the annotation names or uid if annotation does not exist', async () => { @@ -185,7 +180,7 @@ describe('AnnotationsSettings', () => { test('Adding a new annotation', async () => { setup(dashboard); - await userEvent.click(screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add annotation query'))); + await userEvent.click(screen.getByRole('button', { name: 'Add annotation query' })); expect(locationService.getSearchObject().editIndex).toBe('1'); expect(dashboard.annotations.list.length).toBe(2); diff --git a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx index f4547523075..9e1b14a33ed 100644 --- a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx @@ -5,7 +5,6 @@ import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; -import { selectors } from '@grafana/e2e-selectors'; import { locationService } from '@grafana/runtime'; import { GrafanaContext } from 'app/core/context/GrafanaContext'; @@ -93,9 +92,7 @@ describe('LinksSettings', () => { const linksTab = screen.getByRole('tab', { name: 'Tab Links' }); expect(linksTab).toBeInTheDocument(); expect(linksTab).toHaveAttribute('aria-selected', 'true'); - expect( - screen.getByTestId(selectors.components.CallToActionCard.buttonV2('Add dashboard link')) - ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Add dashboard link' })).toBeInTheDocument(); expect(screen.queryByRole('table')).not.toBeInTheDocument(); }); @@ -104,9 +101,7 @@ describe('LinksSettings', () => { setup(dashboard); expect(getTableBodyRows().length).toBe(dashboard.links.length); - expect( - screen.queryByTestId(selectors.components.CallToActionCard.buttonV2('Add dashboard link')) - ).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Add dashboard link' })).not.toBeInTheDocument(); }); test('it rearranges the order of dashboard links', async () => { diff --git a/public/app/features/datasources/components/DataSourcesList.tsx b/public/app/features/datasources/components/DataSourcesList.tsx index ac15fd93cb6..97c2d7f4690 100644 --- a/public/app/features/datasources/components/DataSourcesList.tsx +++ b/public/app/features/datasources/components/DataSourcesList.tsx @@ -4,10 +4,9 @@ import { useLocation } from 'react-router-dom'; import { DataSourceSettings, GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { EmptyState, useStyles2 } from '@grafana/ui'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { EmptyState, LinkButton, TextLink, useStyles2 } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; -import { t } from 'app/core/internationalization'; +import { Trans, t } from 'app/core/internationalization'; import { StoreState, AccessControlAction, useSelector } from 'app/types'; import { getDataSources, getDataSourcesCount, useDataSourcesRoutes, useLoadDataSources } from '../state'; @@ -67,17 +66,25 @@ export function DataSourcesListView({ if (!isLoading && dataSourcesCount === 0) { return ( - + + Add data source + + } + message={t('data-source-list.empty-state.title', 'No data sources defined')} + > + + You can also define data sources through configuration files.{' '} + + Learn more + + + ); } diff --git a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx index 862f34739d9..15d0a9699fa 100644 --- a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx @@ -105,7 +105,7 @@ describe('LibraryPanelsSearch', () => { await getTestContext(); expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument(); - expect(screen.getByText(/no library panels found/i)).toBeInTheDocument(); + expect(screen.getByText(/you haven\'t created any library panels yet/i)).toBeInTheDocument(); }); describe('and user searches for library panel by name or description', () => { @@ -132,7 +132,7 @@ describe('LibraryPanelsSearch', () => { await getTestContext({ showSort: true }); expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument(); - expect(screen.getByText(/no library panels found/i)).toBeInTheDocument(); + expect(screen.getByText(/you haven\'t created any library panels yet/i)).toBeInTheDocument(); expect(screen.getByText(/sort \(default a–z\)/i)).toBeInTheDocument(); }); @@ -160,7 +160,7 @@ describe('LibraryPanelsSearch', () => { await getTestContext({ showPanelFilter: true }); expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument(); - expect(screen.getByText(/no library panels found/i)).toBeInTheDocument(); + expect(screen.getByText(/you haven\'t created any library panels yet/i)).toBeInTheDocument(); expect(screen.getByRole('combobox', { name: /panel type filter/i })).toBeInTheDocument(); }); @@ -188,7 +188,7 @@ describe('LibraryPanelsSearch', () => { await getTestContext({ showFolderFilter: true }); expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument(); - expect(screen.getByText(/no library panels found/i)).toBeInTheDocument(); + expect(screen.getByText(/you haven\'t created any library panels yet/i)).toBeInTheDocument(); expect(screen.getByRole('combobox', { name: /folder filter/i })).toBeInTheDocument(); }); @@ -274,7 +274,7 @@ describe('LibraryPanelsSearch', () => { const card = () => screen.getByLabelText(/plugin visualization item time series/i); - expect(screen.queryByText(/no library panels found/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/you haven\'t created any library panels yet/i)).not.toBeInTheDocument(); expect(card()).toBeInTheDocument(); expect(within(card()).getByText(/library panel name/i)).toBeInTheDocument(); expect(within(card()).getByText(/library panel description/i)).toBeInTheDocument(); @@ -315,7 +315,7 @@ describe('LibraryPanelsSearch', () => { const card = () => screen.getByLabelText(/plugin visualization item time series/i); - expect(screen.queryByText(/no library panels found/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/you haven\'t created any library panels yet/i)).not.toBeInTheDocument(); expect(card()).toBeInTheDocument(); expect(within(card()).getByText(/library panel name/i)).toBeInTheDocument(); expect(within(card()).getByText(/library panel description/i)).toBeInTheDocument(); diff --git a/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx b/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx index a401ba5bf72..22b7ae79026 100644 --- a/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsView/LibraryPanelsView.tsx @@ -3,8 +3,8 @@ import React, { useMemo, useReducer } from 'react'; import { useDebounce } from 'react-use'; import { GrafanaTheme2, LoadingState } from '@grafana/data'; -import { EmptyState, Pagination, Stack, useStyles2 } from '@grafana/ui'; -import { t } from 'app/core/internationalization'; +import { EmptyState, Pagination, Stack, TextLink, useStyles2 } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; import { LibraryElementDTO } from '../../types'; import { LibraryPanelCard } from '../LibraryPanelCard/LibraryPanelCard'; @@ -74,6 +74,26 @@ export const LibraryPanelsView = ({ }) ); const onPageChange = (page: number) => asyncDispatch(changePage({ page })); + const hasFilter = searchString || panelFilter?.length || folderFilter?.length; + + if (!hasFilter && loadingState === LoadingState.Done && libraryPanels.length < 1) { + return ( + + + Create a library panel from any existing dashboard panel through the panel context menu.{' '} + + Learn more + + + + ); + } return ( diff --git a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.test.tsx b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.test.tsx index 6792db27a35..0e395959ad3 100644 --- a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.test.tsx +++ b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.test.tsx @@ -99,13 +99,13 @@ const renderPublicDashboardTable = async (waitForListRendering?: boolean) => { ); - waitForListRendering && (await waitForElementToBeRemoved(screen.getAllByTestId('Spinner')[1], { timeout: 3000 })); + waitForListRendering && (await waitForElementToBeRemoved(screen.getAllByTestId('Spinner')[0], { timeout: 3000 })); }; describe('Show table', () => { it('renders loader spinner while loading', async () => { await renderPublicDashboardTable(); - const spinner = screen.getAllByTestId('Spinner')[1]; + const spinner = screen.getAllByTestId('Spinner')[0]; expect(spinner).toBeInTheDocument(); await waitForElementToBeRemoved(spinner); diff --git a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx index 5486f77cf75..c0a6d4e4d1f 100644 --- a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx +++ b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import React, { useMemo, useState } from 'react'; import { useMedia } from 'react-use'; -import { GrafanaTheme2 } from '@grafana/data/src'; +import { GrafanaTheme2 } from '@grafana/data'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; import { reportInteraction } from '@grafana/runtime'; import { @@ -16,7 +16,9 @@ import { Switch, Pagination, HorizontalGroup, -} from '@grafana/ui/src'; + EmptyState, + TextLink, +} from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { Trans, t } from 'app/core/internationalization'; import { contextSrv } from 'app/core/services/context_srv'; @@ -141,28 +143,50 @@ export const PublicDashboardListTable = () => { const [page, setPage] = useState(1); const styles = useStyles2(getStyles); - const { data: paginatedPublicDashboards, isLoading, isFetching, isError } = useListPublicDashboardsQuery(page); + const { data: paginatedPublicDashboards, isLoading, isError } = useListPublicDashboardsQuery(page); return ( - }> + {!isLoading && !isError && !!paginatedPublicDashboards && (
-
    - {paginatedPublicDashboards.publicDashboards.map((pd: PublicDashboardListResponse) => ( -
  • - -
  • - ))} -
- - - + {paginatedPublicDashboards.publicDashboards.length === 0 ? ( + + + Create a public dashboard from any existing dashboard through the Share modal.{' '} + + Learn more + + + + ) : ( + <> +
    + {paginatedPublicDashboards.publicDashboards.map((pd: PublicDashboardListResponse) => ( +
  • + +
  • + ))} +
+ + + + + )}
)}
diff --git a/public/app/features/manage-dashboards/components/SnapshotListTable.tsx b/public/app/features/manage-dashboards/components/SnapshotListTable.tsx index 37e1d7d03c3..aeac45764c1 100644 --- a/public/app/features/manage-dashboards/components/SnapshotListTable.tsx +++ b/public/app/features/manage-dashboards/components/SnapshotListTable.tsx @@ -2,13 +2,13 @@ import React, { useState, useCallback } from 'react'; import useAsync from 'react-use/lib/useAsync'; import { config } from '@grafana/runtime'; -import { ConfirmModal } from '@grafana/ui'; -import { Trans } from 'app/core/internationalization'; +import { ConfirmModal, EmptyState, TextLink } from '@grafana/ui'; +import { Trans, t } from 'app/core/internationalization'; import { getDashboardSnapshotSrv, Snapshot } from 'app/features/dashboard/services/SnapshotSrv'; import { SnapshotListTableRow } from './SnapshotListTableRow'; -export function getSnapshots() { +export async function getSnapshots() { return getDashboardSnapshotSrv() .getSnapshots() .then((result: Snapshot[]) => { @@ -42,6 +42,25 @@ export const SnapshotListTable = () => { [snapshots] ); + if (!isFetching && snapshots.length === 0) { + return ( + + + You can create a snapshot of any dashboard through the Share modal.{' '} + + Learn more + + + + ); + } + return (
diff --git a/public/app/features/playlist/PlaylistPage.tsx b/public/app/features/playlist/PlaylistPage.tsx index bebb5f3c906..f8cb99c6c77 100644 --- a/public/app/features/playlist/PlaylistPage.tsx +++ b/public/app/features/playlist/PlaylistPage.tsx @@ -1,8 +1,7 @@ import React, { useMemo, useState } from 'react'; import { useAsync } from 'react-use'; -import { ConfirmModal, EmptyState, LinkButton } from '@grafana/ui'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { ConfirmModal, EmptyState, LinkButton, TextLink } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import PageActionBar from 'app/core/components/PageActionBar/PageActionBar'; import { Trans, t } from 'app/core/internationalization'; @@ -35,20 +34,6 @@ export const PlaylistPage = () => { }); }; - const emptyListBanner = ( - - ); - const showSearch = allPlaylists.loading || playlists.length > 0 || searchQuery.length > 0; return ( @@ -78,7 +63,24 @@ export const PlaylistPage = () => { setPlaylistToDelete={setPlaylistToDelete} /> )} - {!showSearch && emptyListBanner} + {!showSearch && ( + + Create playlist + + } + message={t('playlist-page.empty.title', 'There are no playlists created yet')} + > + + You can use playlists to cycle dashboards on TVs without user control.{' '} + + Learn more + + + + )} {playlistToDelete && ( )} {!isLoading && noServiceAccountsCreated && ( - <> - - + + Add service account + + } + message={t('service-accounts.empty-state.title', "You haven't created any service accounts yet")} + > + + Remember, you can provide specific permissions for API access to other applications + + )} {(isLoading || serviceAccounts.length !== 0) && ( diff --git a/public/app/features/teams/TeamList.tsx b/public/app/features/teams/TeamList.tsx index 9830d3b7248..4940ec5bb60 100644 --- a/public/app/features/teams/TeamList.tsx +++ b/public/app/features/teams/TeamList.tsx @@ -19,10 +19,9 @@ import { TextLink, useStyles2, } from '@grafana/ui'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { Page } from 'app/core/components/Page/Page'; import { fetchRoleOptions } from 'app/core/components/RolePicker/api'; -import { t } from 'app/core/internationalization'; +import { Trans, t } from 'app/core/internationalization'; import { contextSrv } from 'app/core/services/context_srv'; import { AccessControlAction, Role, StoreState, Team } from 'app/types'; @@ -208,24 +207,31 @@ export const TeamList = ({ - New Team - + !noTeams ? ( + + New Team + + ) : undefined } > {noTeams ? ( - + + New team + + } + message={t('teams.empty-state.title', "You haven't created any teams yet")} + > + + Assign folder and dashboard permissions to teams instead of users to ease administration.{' '} + + Learn more + + + ) : ( <>
diff --git a/public/app/features/variables/editor/VariableEditorList.tsx b/public/app/features/variables/editor/VariableEditorList.tsx index 32720af0ab6..7dc62f8e86d 100644 --- a/public/app/features/variables/editor/VariableEditorList.tsx +++ b/public/app/features/variables/editor/VariableEditorList.tsx @@ -5,8 +5,8 @@ import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; import { TypedVariableModel } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { reportInteraction } from '@grafana/runtime'; -import { Button, useStyles2, Stack } from '@grafana/ui'; -import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; +import { Button, useStyles2, Stack, EmptyState, TextLink } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; import { VariablesDependenciesButton } from '../inspect/VariablesDependenciesButton'; import { UsagesToNetwork, VariableUsageTree } from '../inspect/utils'; @@ -107,30 +107,38 @@ export function VariableEditorList({ function EmptyVariablesList({ onAdd }: { onAdd: () => void }): ReactElement { return ( -
- - Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server - or sensor names in your metric queries you can use variables in their place. Variables are shown as - list boxes at the top of the dashboard. These drop-down lists make it easy to change the data - being displayed in your dashboard. Check out the - - Templates and variables documentation - - for more information. -

`, - }} - infoBoxTitle="What do variables do?" - onClick={(event) => { - event.preventDefault(); - onAdd(); - }} - /> -
+ + + Add variable + + } + message={t('variables.empty-state.title', 'There are no variables added yet')} + > +

+ + Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server or + sensor names in your metric queries you can use variables in their place. Variables are shown as list boxes + at the top of the dashboard. These drop-down lists make it easy to change the data being displayed in your + dashboard. + +

+ + Check out the{' '} + + Templates and variables documentation + {' '} + for more information. + +
+
); } diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 1e02e4d8c1d..b783638694f 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -25,6 +25,14 @@ "user": "User" } }, + "annotations": { + "empty-state": { + "button-title": "Add annotation query", + "info-box-content": "<0>Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines and icons on all graph panels. When you hover over an annotation icon you can get event text & tags for the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or drag region). These will be stored in Grafana's annotation database.", + "info-box-content-2": "Checkout the <2>Annotations documentation for more information.", + "title": "There are no custom annotation queries added yet" + } + }, "api-keys": { "empty-state": { "message": "No API keys found" @@ -72,6 +80,12 @@ "select-checkbox": "Select", "tags-column": "Tags" }, + "empty-state": { + "button-title": "Create dashboard", + "pro-tip": "Add/move dashboards to your folder at <2>Browse dashboards", + "title": "You haven't created any dashboards yet", + "title-folder": "This folder doesn't have any dashboards yet" + }, "folder-actions-button": { "delete": "Delete", "folder-actions": "Folder actions", @@ -159,6 +173,11 @@ "sub-text": "<0>Define text that will describe the correlation.", "title": "Define correlation label (Step 1 of 3)" }, + "empty-state": { + "button-title": "Add correlation", + "pro-tip": "You can also define correlations via datasource provisioning", + "title": "You haven't defined any correlations yet" + }, "list": { "delete": "delete correlation", "label": "Label", @@ -355,6 +374,13 @@ "validation-required": "Need a dashboard JSON model" } }, + "dashboard-links": { + "empty-state": { + "button-title": "Add dashboard link", + "info-box-content": "Dashboard links allow you to place links to other dashboards and web sites directly below the dashboard header. <2>Learn more", + "title": "There are no dashboard links added yet" + } + }, "dashboard-settings": { "annotations": { "title": "Annotations" @@ -405,6 +431,13 @@ "title": "Versions" } }, + "data-source-list": { + "empty-state": { + "button-title": "Add data source", + "pro-tip": "You can also define data sources through configuration files. <2>Learn more", + "title": "No data sources defined" + } + }, "data-source-picker": { "add-new-data-source": "Configure a new data source", "built-in-list": { @@ -657,6 +690,10 @@ }, "add-widget": { "title": "Add panel from panel library" + }, + "empty-state": { + "message": "You haven't created any library panels yet", + "more-info": "Create a library panel from any existing dashboard panel through the panel context menu. <2>Learn more" } }, "library-panels": { @@ -1230,9 +1267,8 @@ "confirm-text": "Delete" }, "empty": { - "button": "Create Playlist", - "pro-tip": "You can use playlists to cycle dashboards on TVs without user control", - "pro-tip-link-title": "Learn more", + "button": "Create playlist", + "pro-tip": "You can use playlists to cycle dashboards on TVs without user control. <2>Learn more", "title": "There are no playlists created yet" } }, @@ -1346,6 +1382,10 @@ "orphaned-title": "<0>Orphaned public dashboard", "orphaned-tooltip": "The linked dashboard has already been deleted" }, + "empty-state": { + "message": "You haven't created any public dashboards yet", + "more-info": "Create a public dashboard from any existing dashboard through the <1>Share modal. <4>Learn more" + }, "toggle": { "pause-sharing-toggle-text": "Pause sharing" } @@ -1450,7 +1490,10 @@ }, "service-accounts": { "empty-state": { - "message": "No services accounts found" + "button-title": "Add service account", + "message": "No services accounts found", + "more-info": "Remember, you can provide specific permissions for API access to other applications", + "title": "You haven't created any service accounts yet" } }, "share-modal": { @@ -1570,7 +1613,17 @@ }, "title": "Preferences" }, + "silences": { + "empty-state": { + "button-title": "Create silence", + "title": "You haven't created any silences yet" + } + }, "snapshot": { + "empty-state": { + "message": "You haven't created any snapshots yet", + "more-info": "You can create a snapshot of any dashboard through the <1>Share modal. <4>Learn more" + }, "external-badge": "External", "name-column-header": "Name", "url-column-header": "Snapshot url", @@ -1583,7 +1636,10 @@ }, "teams": { "empty-state": { - "message": "No teams found" + "button-title": "New team", + "message": "No teams found", + "pro-tip": "Assign folder and dashboard permissions to teams instead of users to ease administration. <2>Learn more", + "title": "You haven't created any teams yet" } }, "time-picker": { @@ -1705,5 +1761,13 @@ "textbox": { "placeholder": "Enter variable value" } + }, + "variables": { + "empty-state": { + "button-title": "Add variable", + "info-box-content": "Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server or sensor names in your metric queries you can use variables in their place. Variables are shown as list boxes at the top of the dashboard. These drop-down lists make it easy to change the data being displayed in your dashboard.", + "info-box-content-2": "Check out the <2>Templates and variables documentation for more information.", + "title": "There are no variables added yet" + } } } diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 07cb64d01df..9fa2685c1ed 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -25,6 +25,14 @@ "user": "Ůşęř" } }, + "annotations": { + "empty-state": { + "button-title": "Åđđ äʼnʼnőŧäŧįőʼn qūęřy", + "info-box-content": "<0>Åʼnʼnőŧäŧįőʼnş přővįđę ä ŵäy ŧő įʼnŧęģřäŧę ęvęʼnŧ đäŧä įʼnŧő yőūř ģřäpĥş. Ŧĥęy äřę vįşūäľįžęđ äş vęřŧįčäľ ľįʼnęş äʼnđ įčőʼnş őʼn äľľ ģřäpĥ päʼnęľş. Ŵĥęʼn yőū ĥővęř ővęř äʼn äʼnʼnőŧäŧįőʼn įčőʼn yőū čäʼn ģęŧ ęvęʼnŧ ŧęχŧ & ŧäģş ƒőř ŧĥę ęvęʼnŧ. Ÿőū čäʼn äđđ äʼnʼnőŧäŧįőʼn ęvęʼnŧş đįřęčŧľy ƒřőm ģřäƒäʼnä þy ĥőľđįʼnģ CŦŖĿ őř CMĐ + čľįčĸ őʼn ģřäpĥ (őř đřäģ řęģįőʼn). Ŧĥęşę ŵįľľ þę şŧőřęđ įʼn Ğřäƒäʼnä'ş äʼnʼnőŧäŧįőʼn đäŧäþäşę.", + "info-box-content-2": "Cĥęčĸőūŧ ŧĥę <2>Åʼnʼnőŧäŧįőʼnş đőčūmęʼnŧäŧįőʼn ƒőř mőřę įʼnƒőřmäŧįőʼn.", + "title": "Ŧĥęřę äřę ʼnő čūşŧőm äʼnʼnőŧäŧįőʼn qūęřįęş äđđęđ yęŧ" + } + }, "api-keys": { "empty-state": { "message": "Ńő ÅPĨ ĸęyş ƒőūʼnđ" @@ -72,6 +80,12 @@ "select-checkbox": "Ŝęľęčŧ", "tags-column": "Ŧäģş" }, + "empty-state": { + "button-title": "Cřęäŧę đäşĥþőäřđ", + "pro-tip": "Åđđ/mővę đäşĥþőäřđş ŧő yőūř ƒőľđęř äŧ <2>ßřőŵşę đäşĥþőäřđş", + "title": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny đäşĥþőäřđş yęŧ", + "title-folder": "Ŧĥįş ƒőľđęř đőęşʼn'ŧ ĥävę äʼny đäşĥþőäřđş yęŧ" + }, "folder-actions-button": { "delete": "Đęľęŧę", "folder-actions": "Főľđęř äčŧįőʼnş", @@ -159,6 +173,11 @@ "sub-text": "<0>Đęƒįʼnę ŧęχŧ ŧĥäŧ ŵįľľ đęşčřįþę ŧĥę čőřřęľäŧįőʼn.", "title": "Đęƒįʼnę čőřřęľäŧįőʼn ľäþęľ (Ŝŧęp 1 őƒ 3)" }, + "empty-state": { + "button-title": "Åđđ čőřřęľäŧįőʼn", + "pro-tip": "Ÿőū čäʼn äľşő đęƒįʼnę čőřřęľäŧįőʼnş vįä đäŧäşőūřčę přővįşįőʼnįʼnģ", + "title": "Ÿőū ĥävęʼn'ŧ đęƒįʼnęđ äʼny čőřřęľäŧįőʼnş yęŧ" + }, "list": { "delete": "đęľęŧę čőřřęľäŧįőʼn", "label": "Ŀäþęľ", @@ -355,6 +374,13 @@ "validation-required": "Ńęęđ ä đäşĥþőäřđ ĴŜØŃ mőđęľ" } }, + "dashboard-links": { + "empty-state": { + "button-title": "Åđđ đäşĥþőäřđ ľįʼnĸ", + "info-box-content": "Đäşĥþőäřđ ľįʼnĸş äľľőŵ yőū ŧő pľäčę ľįʼnĸş ŧő őŧĥęř đäşĥþőäřđş äʼnđ ŵęþ şįŧęş đįřęčŧľy þęľőŵ ŧĥę đäşĥþőäřđ ĥęäđęř. <2>Ŀęäřʼn mőřę", + "title": "Ŧĥęřę äřę ʼnő đäşĥþőäřđ ľįʼnĸş äđđęđ yęŧ" + } + }, "dashboard-settings": { "annotations": { "title": "Åʼnʼnőŧäŧįőʼnş" @@ -405,6 +431,13 @@ "title": "Vęřşįőʼnş" } }, + "data-source-list": { + "empty-state": { + "button-title": "Åđđ đäŧä şőūřčę", + "pro-tip": "Ÿőū čäʼn äľşő đęƒįʼnę đäŧä şőūřčęş ŧĥřőūģĥ čőʼnƒįģūřäŧįőʼn ƒįľęş. <2>Ŀęäřʼn mőřę", + "title": "Ńő đäŧä şőūřčęş đęƒįʼnęđ" + } + }, "data-source-picker": { "add-new-data-source": "Cőʼnƒįģūřę ä ʼnęŵ đäŧä şőūřčę", "built-in-list": { @@ -657,6 +690,10 @@ }, "add-widget": { "title": "Åđđ päʼnęľ ƒřőm päʼnęľ ľįþřäřy" + }, + "empty-state": { + "message": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny ľįþřäřy päʼnęľş yęŧ", + "more-info": "Cřęäŧę ä ľįþřäřy päʼnęľ ƒřőm äʼny ęχįşŧįʼnģ đäşĥþőäřđ päʼnęľ ŧĥřőūģĥ ŧĥę päʼnęľ čőʼnŧęχŧ męʼnū. <2>Ŀęäřʼn mőřę" } }, "library-panels": { @@ -1230,9 +1267,8 @@ "confirm-text": "Đęľęŧę" }, "empty": { - "button": "Cřęäŧę Pľäyľįşŧ", - "pro-tip": "Ÿőū čäʼn ūşę pľäyľįşŧş ŧő čyčľę đäşĥþőäřđş őʼn ŦVş ŵįŧĥőūŧ ūşęř čőʼnŧřőľ", - "pro-tip-link-title": "Ŀęäřʼn mőřę", + "button": "Cřęäŧę pľäyľįşŧ", + "pro-tip": "Ÿőū čäʼn ūşę pľäyľįşŧş ŧő čyčľę đäşĥþőäřđş őʼn ŦVş ŵįŧĥőūŧ ūşęř čőʼnŧřőľ. <2>Ŀęäřʼn mőřę", "title": "Ŧĥęřę äřę ʼnő pľäyľįşŧş čřęäŧęđ yęŧ" } }, @@ -1346,6 +1382,10 @@ "orphaned-title": "<0>Øřpĥäʼnęđ pūþľįč đäşĥþőäřđ", "orphaned-tooltip": "Ŧĥę ľįʼnĸęđ đäşĥþőäřđ ĥäş äľřęäđy þęęʼn đęľęŧęđ" }, + "empty-state": { + "message": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny pūþľįč đäşĥþőäřđş yęŧ", + "more-info": "Cřęäŧę ä pūþľįč đäşĥþőäřđ ƒřőm äʼny ęχįşŧįʼnģ đäşĥþőäřđ ŧĥřőūģĥ ŧĥę <1>Ŝĥäřę mőđäľ. <4>Ŀęäřʼn mőřę" + }, "toggle": { "pause-sharing-toggle-text": "Päūşę şĥäřįʼnģ" } @@ -1450,7 +1490,10 @@ }, "service-accounts": { "empty-state": { - "message": "Ńő şęřvįčęş äččőūʼnŧş ƒőūʼnđ" + "button-title": "Åđđ şęřvįčę äččőūʼnŧ", + "message": "Ńő şęřvįčęş äččőūʼnŧş ƒőūʼnđ", + "more-info": "Ŗęmęmþęř, yőū čäʼn přővįđę şpęčįƒįč pęřmįşşįőʼnş ƒőř ÅPĨ äččęşş ŧő őŧĥęř äppľįčäŧįőʼnş", + "title": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny şęřvįčę äččőūʼnŧş yęŧ" } }, "share-modal": { @@ -1570,7 +1613,17 @@ }, "title": "Přęƒęřęʼnčęş" }, + "silences": { + "empty-state": { + "button-title": "Cřęäŧę şįľęʼnčę", + "title": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny şįľęʼnčęş yęŧ" + } + }, "snapshot": { + "empty-state": { + "message": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny şʼnäpşĥőŧş yęŧ", + "more-info": "Ÿőū čäʼn čřęäŧę ä şʼnäpşĥőŧ őƒ äʼny đäşĥþőäřđ ŧĥřőūģĥ ŧĥę <1>Ŝĥäřę mőđäľ. <4>Ŀęäřʼn mőřę" + }, "external-badge": "Ēχŧęřʼnäľ", "name-column-header": "Ńämę", "url-column-header": "Ŝʼnäpşĥőŧ ūřľ", @@ -1583,7 +1636,10 @@ }, "teams": { "empty-state": { - "message": "Ńő ŧęämş ƒőūʼnđ" + "button-title": "Ńęŵ ŧęäm", + "message": "Ńő ŧęämş ƒőūʼnđ", + "pro-tip": "Åşşįģʼn ƒőľđęř äʼnđ đäşĥþőäřđ pęřmįşşįőʼnş ŧő ŧęämş įʼnşŧęäđ őƒ ūşęřş ŧő ęäşę äđmįʼnįşŧřäŧįőʼn. <2>Ŀęäřʼn mőřę", + "title": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny ŧęämş yęŧ" } }, "time-picker": { @@ -1705,5 +1761,13 @@ "textbox": { "placeholder": "Ēʼnŧęř väřįäþľę väľūę" } + }, + "variables": { + "empty-state": { + "button-title": "Åđđ väřįäþľę", + "info-box-content": "Väřįäþľęş ęʼnäþľę mőřę įʼnŧęřäčŧįvę äʼnđ đyʼnämįč đäşĥþőäřđş. Ĩʼnşŧęäđ őƒ ĥäřđ-čőđįʼnģ ŧĥįʼnģş ľįĸę şęřvęř őř şęʼnşőř ʼnämęş įʼn yőūř męŧřįč qūęřįęş yőū čäʼn ūşę väřįäþľęş įʼn ŧĥęįř pľäčę. Väřįäþľęş äřę şĥőŵʼn äş ľįşŧ þőχęş äŧ ŧĥę ŧőp őƒ ŧĥę đäşĥþőäřđ. Ŧĥęşę đřőp-đőŵʼn ľįşŧş mäĸę įŧ ęäşy ŧő čĥäʼnģę ŧĥę đäŧä þęįʼnģ đįşpľäyęđ įʼn yőūř đäşĥþőäřđ.", + "info-box-content-2": "Cĥęčĸ őūŧ ŧĥę <2>Ŧęmpľäŧęş äʼnđ väřįäþľęş đőčūmęʼnŧäŧįőʼn ƒőř mőřę įʼnƒőřmäŧįőʼn.", + "title": "Ŧĥęřę äřę ʼnő väřįäþľęş äđđęđ yęŧ" + } } } From de92317fc714ef6f6ace5b24fb98799715cf570c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 17:15:34 +0100 Subject: [PATCH 018/222] Update dependency rc-slider to v10.6.2 (#86704) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 44820a165a0..b57829c2163 100644 --- a/package.json +++ b/package.json @@ -357,7 +357,7 @@ "pseudoizer": "^0.1.0", "rc-cascader": "3.24.1", "rc-drawer": "7.1.0", - "rc-slider": "10.5.0", + "rc-slider": "10.6.2", "rc-time-picker": "3.7.3", "rc-tree": "5.8.5", "re-resizable": "6.9.14", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 0201c430c7d..1d0f7ef94c8 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -80,7 +80,7 @@ "prismjs": "1.29.0", "rc-cascader": "3.24.1", "rc-drawer": "7.1.0", - "rc-slider": "10.5.0", + "rc-slider": "10.6.2", "rc-time-picker": "^3.7.3", "rc-tooltip": "6.2.0", "react-beautiful-dnd": "13.1.1", diff --git a/yarn.lock b/yarn.lock index d0c415bb1c0..a5265fb1f57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4365,7 +4365,7 @@ __metadata: process: "npm:^0.11.10" rc-cascader: "npm:3.24.1" rc-drawer: "npm:7.1.0" - rc-slider: "npm:10.5.0" + rc-slider: "npm:10.6.2" rc-time-picker: "npm:^3.7.3" rc-tooltip: "npm:6.2.0" react: "npm:18.2.0" @@ -18860,7 +18860,7 @@ __metadata: pseudoizer: "npm:^0.1.0" rc-cascader: "npm:3.24.1" rc-drawer: "npm:7.1.0" - rc-slider: "npm:10.5.0" + rc-slider: "npm:10.6.2" rc-time-picker: "npm:3.7.3" rc-tree: "npm:5.8.5" re-resizable: "npm:6.9.14" @@ -26158,17 +26158,17 @@ __metadata: languageName: node linkType: hard -"rc-slider@npm:10.5.0": - version: 10.5.0 - resolution: "rc-slider@npm:10.5.0" +"rc-slider@npm:10.6.2": + version: 10.6.2 + resolution: "rc-slider@npm:10.6.2" dependencies: "@babel/runtime": "npm:^7.10.1" classnames: "npm:^2.2.5" - rc-util: "npm:^5.27.0" + rc-util: "npm:^5.36.0" peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 10/9fe5b45a0e199311d665312e9940a59e007b81bfdd868cfecb70f4f95299b5447ee2a7c5a4c90a0ee7bbffad9dfa73cc01c779946adc4ca4fe22d2cf02bac046 + checksum: 10/ee5ec34fe940487a44cb03e89abf4199cbae990a334756fa9536a158d3a790cb7c1ebf321fc8048050a0cefa86138e097eb11bcc135bdb10db287b0738370790 languageName: node linkType: hard @@ -26244,16 +26244,16 @@ __metadata: languageName: node linkType: hard -"rc-util@npm:^5.15.0, rc-util@npm:^5.16.1, rc-util@npm:^5.21.0, rc-util@npm:^5.24.4, rc-util@npm:^5.27.0, rc-util@npm:^5.37.0, rc-util@npm:^5.38.0, rc-util@npm:^5.38.1": - version: 5.38.2 - resolution: "rc-util@npm:5.38.2" +"rc-util@npm:^5.15.0, rc-util@npm:^5.16.1, rc-util@npm:^5.21.0, rc-util@npm:^5.24.4, rc-util@npm:^5.27.0, rc-util@npm:^5.36.0, rc-util@npm:^5.37.0, rc-util@npm:^5.38.0, rc-util@npm:^5.38.1": + version: 5.39.1 + resolution: "rc-util@npm:5.39.1" dependencies: "@babel/runtime": "npm:^7.18.3" react-is: "npm:^18.2.0" peerDependencies: react: ">=16.9.0" react-dom: ">=16.9.0" - checksum: 10/f8d8b21d0ed09de6fcf6c24dc19bf82f8f1fd089a625d35fd399626280ed33e73b9a703aa78f1a09ccd40b83f50e73bbc993adf892355a01857d3a1bb83e0958 + checksum: 10/475e7755f8a8aaf8428c535e14ad1475d3d764108852a011cb5186fb7d905e064c0d56b77818ddaea0c5fbde439d3606b52f459cef7725fde8abf15e3e2ece2b languageName: node linkType: hard From 9735a8a0808129d024cfeb191de795f684dfe03b Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Mon, 22 Apr 2024 12:28:46 -0400 Subject: [PATCH 019/222] Alerting: Distinguish conflict violation errors (#86634) * update generator to set ID = 0 and do not set 0 if unique is needed * return proper message when the constraint violation --- .../ngalert/models/alert_rule_test.go | 2 +- pkg/services/ngalert/models/testing.go | 4 +-- pkg/services/ngalert/store/alert_rule.go | 15 ++++++-- pkg/services/ngalert/store/alert_rule_test.go | 35 ++++++++++++++----- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/pkg/services/ngalert/models/alert_rule_test.go b/pkg/services/ngalert/models/alert_rule_test.go index 22d2f65a9e6..f875a73d9d7 100644 --- a/pkg/services/ngalert/models/alert_rule_test.go +++ b/pkg/services/ngalert/models/alert_rule_test.go @@ -347,7 +347,7 @@ func TestPatchPartialAlertRule(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { var existing *AlertRule for { - existing = AlertRuleGen()() + existing = AlertRuleGen(WithUniqueID())() cloned := *existing // make sure the generated rule does not match the mutated one testCase.mutator(&cloned) diff --git a/pkg/services/ngalert/models/testing.go b/pkg/services/ngalert/models/testing.go index 71ffd9312a9..f22f357f251 100644 --- a/pkg/services/ngalert/models/testing.go +++ b/pkg/services/ngalert/models/testing.go @@ -71,7 +71,7 @@ func AlertRuleGen(mutators ...AlertRuleMutator) func() *AlertRule { } rule := &AlertRule{ - ID: rand.Int63n(1500), + ID: 0, OrgID: rand.Int63n(1500) + 1, // Prevent OrgID=0 as this does not pass alert rule validation. Title: "TEST-ALERT-" + util.GenerateShortUID(), Condition: "A", @@ -110,7 +110,7 @@ func WithUniqueID() AlertRuleMutator { usedID := make(map[int64]struct{}) return func(rule *AlertRule) { for { - id := rand.Int63n(1500) + id := rand.Int63n(1500) + 1 if _, ok := usedID[id]; !ok { usedID[id] = struct{}{} rule.ID = id diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 2570657ea01..0ae77f063d5 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -169,7 +169,7 @@ func (st DBstore) InsertAlertRules(ctx context.Context, rules []ngmodels.AlertRu for i := range newRules { if _, err := sess.Insert(&newRules[i]); err != nil { if st.SQLStore.GetDialect().IsUniqueConstraintViolation(err) { - return ngmodels.ErrAlertRuleConflict(newRules[i], ngmodels.ErrAlertRuleUniqueConstraintViolation) + return ruleConstraintViolationToErr(newRules[i], err) } return fmt.Errorf("failed to create new rules: %w", err) } @@ -215,7 +215,7 @@ func (st DBstore) UpdateAlertRules(ctx context.Context, rules []ngmodels.UpdateR if updated, err := sess.ID(r.Existing.ID).AllCols().Update(r.New); err != nil || updated == 0 { if err != nil { if st.SQLStore.GetDialect().IsUniqueConstraintViolation(err) { - return ngmodels.ErrAlertRuleConflict(r.New, ngmodels.ErrAlertRuleUniqueConstraintViolation) + return ruleConstraintViolationToErr(r.New, err) } return fmt.Errorf("failed to update rule [%s] %s: %w", r.New.UID, r.New.Title, err) } @@ -758,3 +758,14 @@ func (st DBstore) RenameReceiverInNotificationSettings(ctx context.Context, orgI } return len(updates), st.UpdateAlertRules(ctx, updates) } + +func ruleConstraintViolationToErr(rule ngmodels.AlertRule, err error) error { + msg := err.Error() + if strings.Contains(msg, "UQE_alert_rule_org_id_namespace_uid_title") || strings.Contains(msg, "alert_rule.org_id, alert_rule.namespace_uid, alert_rule.title") { + return ngmodels.ErrAlertRuleConflict(rule, ngmodels.ErrAlertRuleUniqueConstraintViolation) + } else if strings.Contains(msg, "UQE_alert_rule_org_id_uid") || strings.Contains(msg, "alert_rule.org_id, alert_rule.uid") { + return ngmodels.ErrAlertRuleConflict(rule, errors.New("rule UID under the same organisation should be unique")) + } else { + return ngmodels.ErrAlertRuleConflict(rule, err) + } +} diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index 97aa3433d3e..fe470991d73 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -603,6 +603,7 @@ func TestIntegrationInsertAlertRules(t *testing.T) { t.Skip("skipping integration test") } + orgID := int64(1) sqlStore := db.InitTestDB(t) cfg := setting.NewCfg() cfg.UnifiedAlerting.BaseInterval = 1 * time.Second @@ -613,7 +614,7 @@ func TestIntegrationInsertAlertRules(t *testing.T) { Cfg: cfg.UnifiedAlerting, } - rules := models.GenerateAlertRules(5, models.AlertRuleGen(models.WithOrgID(1), withIntervalMatching(store.Cfg.BaseInterval))) + rules := models.GenerateAlertRules(5, models.AlertRuleGen(models.WithOrgID(orgID), withIntervalMatching(store.Cfg.BaseInterval))) deref := make([]models.AlertRule, 0, len(rules)) for _, rule := range rules { deref = append(deref, *rule) @@ -641,14 +642,30 @@ func TestIntegrationInsertAlertRules(t *testing.T) { require.Truef(t, found, "Rule with key %#v was not found in database", keyWithID) } - _, err = store.InsertAlertRules(context.Background(), []models.AlertRule{deref[0]}) - require.ErrorIs(t, err, models.ErrAlertRuleUniqueConstraintViolation) - require.NotEqual(t, deref[0].UID, "") - require.NotEqual(t, deref[0].Title, "") - require.NotEqual(t, deref[0].NamespaceUID, "") - require.ErrorContains(t, err, deref[0].UID) - require.ErrorContains(t, err, deref[0].Title) - require.ErrorContains(t, err, deref[0].NamespaceUID) + t.Run("fail to insert rules with same ID", func(t *testing.T) { + _, err = store.InsertAlertRules(context.Background(), []models.AlertRule{deref[0]}) + require.ErrorIs(t, err, models.ErrAlertRuleConflictBase) + }) + t.Run("fail insert rules with the same title in a folder", func(t *testing.T) { + cp := models.CopyRule(&deref[0]) + cp.UID = cp.UID + "-new" + _, err = store.InsertAlertRules(context.Background(), []models.AlertRule{*cp}) + require.ErrorIs(t, err, models.ErrAlertRuleConflictBase) + require.ErrorIs(t, err, models.ErrAlertRuleUniqueConstraintViolation) + require.NotEqual(t, deref[0].UID, "") + require.NotEqual(t, deref[0].Title, "") + require.NotEqual(t, deref[0].NamespaceUID, "") + require.ErrorContains(t, err, deref[0].UID) + require.ErrorContains(t, err, deref[0].Title) + require.ErrorContains(t, err, deref[0].NamespaceUID) + }) + t.Run("should not let insert rules with the same UID", func(t *testing.T) { + cp := models.CopyRule(&deref[0]) + cp.Title = "unique-test-title" + _, err = store.InsertAlertRules(context.Background(), []models.AlertRule{*cp}) + require.ErrorIs(t, err, models.ErrAlertRuleConflictBase) + require.ErrorContains(t, err, "rule UID under the same organisation should be unique") + }) } func TestIntegrationAlertRulesNotificationSettings(t *testing.T) { From 59eb302fc14e54b1889a5df0431d97b51f25bd7f Mon Sep 17 00:00:00 2001 From: Lisa <60980933+LisaHJung@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:38:11 -0600 Subject: [PATCH 020/222] Embed Managing users and permissions video to the documentation (#86387) Co-authored-by: Jack Baldry --- docs/sources/administration/user-management/_index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/sources/administration/user-management/_index.md b/docs/sources/administration/user-management/_index.md index 27ca8ec0673..ad3be03f7cb 100644 --- a/docs/sources/administration/user-management/_index.md +++ b/docs/sources/administration/user-management/_index.md @@ -13,6 +13,10 @@ weight: 200 A _user_ is defined as any individual who can log in to Grafana. Each user is associated with a _role_ that includes _permissions_. Permissions determine the tasks a user can perform in the system. For example, the **Admin** role includes permissions for an administrator to create and delete users. +Watch the following video to learn how to manage users and permissions in Grafana OSS and Grafana Cloud: + +{{< youtube id="59uCGJN5hPI" >}} + The following topics describe how to use permissions to control user access to data sources, dashboards, users, and teams. {{< section >}} From c32953e52cf9fd46a462711bc23fde15a5c3b6bf Mon Sep 17 00:00:00 2001 From: Alexander Weaver Date: Mon, 22 Apr 2024 12:53:16 -0500 Subject: [PATCH 021/222] Alertign: Create feature toggle for recording rules (#86696) create toggle for recording rules --- .../grafana-data/src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/registry.go | 9 +++++++++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++++ pkg/services/featuremgmt/toggles_gen.json | 14 ++++++++++++++ 5 files changed, 29 insertions(+) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 63ff19f663e..ecdc7a62038 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -179,4 +179,5 @@ export interface FeatureToggles { cloudWatchNewLabelParsing?: boolean; accessActionSets?: boolean; disableNumericMetricsSortingInExpressions?: boolean; + grafanaManagedRecordingRules?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 0e6575e57c3..f6220d8dba3 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1201,6 +1201,15 @@ var ( Owner: grafanaObservabilityMetricsSquad, RequiresRestart: true, }, + { + Name: "grafanaManagedRecordingRules", + Description: "Enables Grafana-managed recording rules.", + Stage: FeatureStageExperimental, + Owner: grafanaAlertingSquad, + AllowSelfServe: false, + HideFromDocs: true, + HideFromAdminPage: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index b6781497f9d..d5a41c349fc 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -160,3 +160,4 @@ newDashboardWithFiltersAndGroupBy,experimental,@grafana/dashboards-squad,false,f cloudWatchNewLabelParsing,GA,@grafana/aws-datasources,false,false,false accessActionSets,experimental,@grafana/identity-access-team,false,false,false disableNumericMetricsSortingInExpressions,experimental,@grafana/observability-metrics,false,true,false +grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index e63bf491bc6..d98b2823705 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -650,4 +650,8 @@ const ( // FlagDisableNumericMetricsSortingInExpressions // In server-side expressions, disable the sorting of numeric-kind metrics by their metric name or labels. FlagDisableNumericMetricsSortingInExpressions = "disableNumericMetricsSortingInExpressions" + + // FlagGrafanaManagedRecordingRules + // Enables Grafana-managed recording rules. + FlagGrafanaManagedRecordingRules = "grafanaManagedRecordingRules" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index ee818f42a22..637ae216736 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2071,6 +2071,20 @@ "codeowner": "@grafana/dataviz-squad", "frontend": true } + }, + { + "metadata": { + "name": "grafanaManagedRecordingRules", + "resourceVersion": "1713795659477", + "creationTimestamp": "2024-04-22T14:20:59Z" + }, + "spec": { + "description": "Enables Grafana-managed recording rules.", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad", + "hideFromAdminPage": true, + "hideFromDocs": true + } } ] } \ No newline at end of file From 0b2e748bd83283974ba5265a55bd911c56d78682 Mon Sep 17 00:00:00 2001 From: Fabrizio <135109076+fabrizio-grafana@users.noreply.github.com> Date: Mon, 22 Apr 2024 22:02:56 +0200 Subject: [PATCH 022/222] Loki: Fix setting of tenant ID (#86433) --- pkg/tsdb/loki/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tsdb/loki/api.go b/pkg/tsdb/loki/api.go index f914063f095..49f3d5a94b8 100644 --- a/pkg/tsdb/loki/api.go +++ b/pkg/tsdb/loki/api.go @@ -367,7 +367,7 @@ func setXScopeOrgIDHeader(req *http.Request, ctx context.Context) *http.Request if len(tenantids) == 0 { // We assume we are not using multi-tenant mode, which is fine logger.Debug("Tenant ID not present. Header not set") - } else if len(tenantids[0]) > 1 { + } else if len(tenantids) > 1 { // Loki supports multiple tenant IDs, but we should receive them from different contexts logger.Error(strconv.Itoa(len(tenantids)) + " tenant IDs found. Header not set") } else { From 05a4b3e80d9d352f6eb001bee4be36049b908d13 Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Mon, 22 Apr 2024 17:02:45 -0400 Subject: [PATCH 023/222] Docs: Add config guidance for embedding (#86726) Added note re iframes --- .../panels-visualizations/visualizations/text/index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/sources/panels-visualizations/visualizations/text/index.md b/docs/sources/panels-visualizations/visualizations/text/index.md index 51276ae365a..f9ed03275af 100644 --- a/docs/sources/panels-visualizations/visualizations/text/index.md +++ b/docs/sources/panels-visualizations/visualizations/text/index.md @@ -37,6 +37,10 @@ Use a text visualization when you need to: **Mode** determines how embedded content appears. +{{< admonition type="note" >}} +To allow embedding of iframes and other websites, you need set `allow_embedding = true` in your Grafana `config.ini` or environment variables (depending on your employment). +{{< /admonition >}} + ### Markdown This option formats the content as [markdown](https://en.wikipedia.org/wiki/Markdown). From 2b3457e6edc44a7828130d491e91ea44c76c10eb Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Mon, 22 Apr 2024 18:28:15 -0400 Subject: [PATCH 024/222] Docs: add snapshot deletion info (#86725) * Added delete snapshots section * Replicated content, updated heading, and lowered heading level --- .../share-dashboards-panels/index.md | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/sources/dashboards/share-dashboards-panels/index.md b/docs/sources/dashboards/share-dashboards-panels/index.md index b5a90ba97f5..7fd7a4a4e93 100644 --- a/docs/sources/dashboards/share-dashboards-panels/index.md +++ b/docs/sources/dashboards/share-dashboards-panels/index.md @@ -91,7 +91,17 @@ You can publish snapshots to your local instance or to [snapshots.raintank.io](h 1. Copy the snapshot link, and share it either within your organization or publicly on the web. -If you created a snapshot by mistake, click **Delete snapshot** to remove the snapshot from your Grafana instance. +If you created a snapshot by mistake, click **Delete snapshot** in the dialog box to remove the snapshot from your Grafana instance. + +#### Delete a snapshot + +To delete existing snapshots, follow these steps: + +1. In the primary menu, click **Dashboards**. +1. Click **Snapshots** to go to the snapshots management page. +1. Click the red **x** next to the snapshot URL that you want to delete. + +The snapshot is immediately deleted. You may need to clear your browser cache or use a private or incognito browser to confirm this. ### Export a dashboard as JSON @@ -183,7 +193,17 @@ You can optionally set an expiration time if you want the snapshot to be removed 1. Copy the snapshot link, and share it either within your organization or publicly on the web. -If you created a snapshot by mistake, click **Delete snapshot** to remove the snapshot from your Grafana instance. +If you created a snapshot by mistake, click **Delete snapshot** in the dialog box to remove the snapshot from your Grafana instance. + +#### Delete a snapshot + +To delete existing snapshots, follow these steps: + +1. In the primary menu, click **Dashboards**. +1. Click **Snapshots** to go to the snapshots management page. +1. Click the red **x** next to the snapshot URL that you want to delete. + +The snapshot is immediately deleted. You may need to clear your browser cache or use a private or incognito browser to confirm this. ### Embed panel From 7754d0d4dcedc17fdbe528b4d53ef938862fc19a Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Mon, 22 Apr 2024 22:24:39 -0500 Subject: [PATCH 025/222] XYChart2: Remove common series name from tooltip items (#86739) --- .../plugins/panel/xychart/v2/XYChartTooltip.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/panel/xychart/v2/XYChartTooltip.tsx b/public/app/plugins/panel/xychart/v2/XYChartTooltip.tsx index bc80bffe01f..bfc25494772 100644 --- a/public/app/plugins/panel/xychart/v2/XYChartTooltip.tsx +++ b/public/app/plugins/panel/xychart/v2/XYChartTooltip.tsx @@ -23,6 +23,14 @@ export interface Props { xySeries: XYSeries[]; } +function stripSeriesName(fieldName: string, seriesName: string) { + if (fieldName.includes(' ')) { + fieldName = fieldName.replace(seriesName, '').trim(); + } + + return fieldName; +} + export const XYChartTooltip = ({ dataIdxs, seriesIdx, data, xySeries, dismiss, isPinned }: Props) => { const styles = useStyles2(getStyles); @@ -51,18 +59,18 @@ export const XYChartTooltip = ({ dataIdxs, seriesIdx, data, xySeries, dismiss, i const contentItems: VizTooltipItem[] = [ { - label: xField.state?.displayName ?? xField.name, + label: stripSeriesName(xField.state?.displayName ?? xField.name, label), value: fmt(xField, xField.values[rowIndex]), }, { - label: yField.state?.displayName ?? yField.name, + label: stripSeriesName(yField.state?.displayName ?? yField.name, label), value: fmt(yField, yField.values[rowIndex]), }, ]; series._rest.forEach((field) => { contentItems.push({ - label: field.state?.displayName ?? field.name, + label: stripSeriesName(field.state?.displayName ?? field.name, label), value: fmt(field, field.values[rowIndex]), }); }); From 357276da01c45393b8948e289494b831f6effb73 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 07:34:17 +0300 Subject: [PATCH 026/222] I18n: Download translations from Crowdin (#86708) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/de-DE/grafana.json | 74 +++++++++++++++++++++++++++-- public/locales/es-ES/grafana.json | 74 +++++++++++++++++++++++++++-- public/locales/fr-FR/grafana.json | 74 +++++++++++++++++++++++++++-- public/locales/pt-BR/grafana.json | 74 +++++++++++++++++++++++++++-- public/locales/zh-Hans/grafana.json | 74 +++++++++++++++++++++++++++-- 5 files changed, 345 insertions(+), 25 deletions(-) diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index bf7a0308c24..f6ca6a15f1f 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -25,6 +25,14 @@ "user": "Nutzer" } }, + "annotations": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "info-box-content-2": "", + "title": "" + } + }, "api-keys": { "empty-state": { "message": "" @@ -72,6 +80,12 @@ "select-checkbox": "Auswählen", "tags-column": "Tags" }, + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "", + "title-folder": "" + }, "folder-actions-button": { "delete": "Löschen", "folder-actions": "Ordneraktionen", @@ -159,6 +173,11 @@ "sub-text": "<0>Text definieren, der die Korrelation beschreibt.", "title": "Korrelationsbezeichnung definieren (Schritt 1 von 3)" }, + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "" + }, "list": { "delete": "Korrelation löschen", "label": "Label", @@ -355,6 +374,13 @@ "validation-required": "Benötigt ein Dashboard JSON-Modell" } }, + "dashboard-links": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "title": "" + } + }, "dashboard-settings": { "annotations": { "title": "Anmerkungen" @@ -405,6 +431,13 @@ "title": "Versionen" } }, + "data-source-list": { + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "" + } + }, "data-source-picker": { "add-new-data-source": "Neue Datenquelle konfigurieren", "built-in-list": { @@ -657,6 +690,10 @@ }, "add-widget": { "title": "Panel aus Panel-Bibliothek hinzufügen" + }, + "empty-state": { + "message": "", + "more-info": "" } }, "library-panels": { @@ -1230,9 +1267,8 @@ "confirm-text": "Löschen" }, "empty": { - "button": "Playlist erstellen", - "pro-tip": "Sie können Playlists verwenden, um Dashboards auf TV-Geräten ohne Benutzersteuerung zu steuern", - "pro-tip-link-title": "Mehr erfahren", + "button": "", + "pro-tip": "", "title": "Es gibt noch keine Playlists erstellt" } }, @@ -1346,6 +1382,10 @@ "orphaned-title": "<0>Verwaistes öffentliches Dashboard", "orphaned-tooltip": "Das verlinkte Dashboard wurde bereits gelöscht" }, + "empty-state": { + "message": "", + "more-info": "" + }, "toggle": { "pause-sharing-toggle-text": "Teilen pausieren" } @@ -1450,7 +1490,10 @@ }, "service-accounts": { "empty-state": { - "message": "" + "button-title": "", + "message": "", + "more-info": "", + "title": "" } }, "share-modal": { @@ -1570,7 +1613,17 @@ }, "title": "Einstellungen" }, + "silences": { + "empty-state": { + "button-title": "", + "title": "" + } + }, "snapshot": { + "empty-state": { + "message": "", + "more-info": "" + }, "external-badge": "Extern", "name-column-header": "Name", "url-column-header": "Snapshot url", @@ -1583,7 +1636,10 @@ }, "teams": { "empty-state": { - "message": "" + "button-title": "", + "message": "", + "pro-tip": "", + "title": "" } }, "time-picker": { @@ -1705,5 +1761,13 @@ "textbox": { "placeholder": "Variablenwert eingeben" } + }, + "variables": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "info-box-content-2": "", + "title": "" + } } } \ No newline at end of file diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 077fea16e7c..767f9556d53 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -25,6 +25,14 @@ "user": "Usuario" } }, + "annotations": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "info-box-content-2": "", + "title": "" + } + }, "api-keys": { "empty-state": { "message": "" @@ -72,6 +80,12 @@ "select-checkbox": "Seleccionar", "tags-column": "Etiquetas" }, + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "", + "title-folder": "" + }, "folder-actions-button": { "delete": "Eliminar", "folder-actions": "Acciones de la carpeta", @@ -159,6 +173,11 @@ "sub-text": "<0>Defina el texto que describirá la correlación.", "title": "Definir la etiqueta de correlación (paso 1 de 3)" }, + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "" + }, "list": { "delete": "eliminar correlación", "label": "Etiqueta", @@ -355,6 +374,13 @@ "validation-required": "Se necesita un modelo JSON del tablero" } }, + "dashboard-links": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "title": "" + } + }, "dashboard-settings": { "annotations": { "title": "Anotaciones" @@ -405,6 +431,13 @@ "title": "Versiones" } }, + "data-source-list": { + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "" + } + }, "data-source-picker": { "add-new-data-source": "Configurar una nueva fuente de datos", "built-in-list": { @@ -657,6 +690,10 @@ }, "add-widget": { "title": "Añadir panel de la biblioteca de paneles" + }, + "empty-state": { + "message": "", + "more-info": "" } }, "library-panels": { @@ -1230,9 +1267,8 @@ "confirm-text": "Eliminar" }, "empty": { - "button": "Crear lista de reproducción", - "pro-tip": "Puede usar las listas de reproducción para crear ciclos de tableros en televisiones sin control de usuario", - "pro-tip-link-title": "Más información", + "button": "", + "pro-tip": "", "title": "Aún no se ha creado ninguna lista de reproducción" } }, @@ -1346,6 +1382,10 @@ "orphaned-title": "<0>Tablero público huérfano", "orphaned-tooltip": "El tablero vinculado ya se ha eliminado" }, + "empty-state": { + "message": "", + "more-info": "" + }, "toggle": { "pause-sharing-toggle-text": "Pausar el uso compartido" } @@ -1450,7 +1490,10 @@ }, "service-accounts": { "empty-state": { - "message": "" + "button-title": "", + "message": "", + "more-info": "", + "title": "" } }, "share-modal": { @@ -1570,7 +1613,17 @@ }, "title": "Preferencias" }, + "silences": { + "empty-state": { + "button-title": "", + "title": "" + } + }, "snapshot": { + "empty-state": { + "message": "", + "more-info": "" + }, "external-badge": "Externo", "name-column-header": "Nombre", "url-column-header": "URL de la instantánea", @@ -1583,7 +1636,10 @@ }, "teams": { "empty-state": { - "message": "" + "button-title": "", + "message": "", + "pro-tip": "", + "title": "" } }, "time-picker": { @@ -1705,5 +1761,13 @@ "textbox": { "placeholder": "Introducir el valor de la variable" } + }, + "variables": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "info-box-content-2": "", + "title": "" + } } } \ No newline at end of file diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index c054c1a56a0..56557572fb5 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -25,6 +25,14 @@ "user": "Utilisateur" } }, + "annotations": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "info-box-content-2": "", + "title": "" + } + }, "api-keys": { "empty-state": { "message": "" @@ -72,6 +80,12 @@ "select-checkbox": "Sélectionner", "tags-column": "Étiquettes" }, + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "", + "title-folder": "" + }, "folder-actions-button": { "delete": "Supprimer", "folder-actions": "Actions sur le dossier", @@ -159,6 +173,11 @@ "sub-text": "<0>Définissez le texte qui décrira la corrélation.", "title": "Définir l'étiquette de corrélation (Étape 1 sur 3)" }, + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "" + }, "list": { "delete": "supprimer la corrélation", "label": "Étiquette", @@ -355,6 +374,13 @@ "validation-required": "Nécessite un modèle JSON de tableau de bord" } }, + "dashboard-links": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "title": "" + } + }, "dashboard-settings": { "annotations": { "title": "Annotations" @@ -405,6 +431,13 @@ "title": "Versions" } }, + "data-source-list": { + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "" + } + }, "data-source-picker": { "add-new-data-source": "Configurer une nouvelle source de données", "built-in-list": { @@ -657,6 +690,10 @@ }, "add-widget": { "title": "Ajouter un panneau depuis la bibliothèque de panneau" + }, + "empty-state": { + "message": "", + "more-info": "" } }, "library-panels": { @@ -1230,9 +1267,8 @@ "confirm-text": "Supprimer" }, "empty": { - "button": "Créer une playlist", - "pro-tip": "Vous pouvez utiliser des playlists pour faire défiler des tableaux de bord sur des téléviseurs sans contrôle utilisateur", - "pro-tip-link-title": "En savoir plus", + "button": "", + "pro-tip": "", "title": "Il n'y a aucune playlist créée" } }, @@ -1346,6 +1382,10 @@ "orphaned-title": "<0>Tableau de bord public orphelin", "orphaned-tooltip": "Le tableau de bord lié a déjà été supprimé" }, + "empty-state": { + "message": "", + "more-info": "" + }, "toggle": { "pause-sharing-toggle-text": "Suspendre le partage" } @@ -1450,7 +1490,10 @@ }, "service-accounts": { "empty-state": { - "message": "" + "button-title": "", + "message": "", + "more-info": "", + "title": "" } }, "share-modal": { @@ -1570,7 +1613,17 @@ }, "title": "Préférences" }, + "silences": { + "empty-state": { + "button-title": "", + "title": "" + } + }, "snapshot": { + "empty-state": { + "message": "", + "more-info": "" + }, "external-badge": "Externe", "name-column-header": "Nom", "url-column-header": "URL de l'instantané", @@ -1583,7 +1636,10 @@ }, "teams": { "empty-state": { - "message": "" + "button-title": "", + "message": "", + "pro-tip": "", + "title": "" } }, "time-picker": { @@ -1705,5 +1761,13 @@ "textbox": { "placeholder": "Entrer la valeur de la variable" } + }, + "variables": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "info-box-content-2": "", + "title": "" + } } } \ No newline at end of file diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 5cc66282b4a..06cc77e8e42 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -25,6 +25,14 @@ "user": "Usuário" } }, + "annotations": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "info-box-content-2": "", + "title": "" + } + }, "api-keys": { "empty-state": { "message": "" @@ -72,6 +80,12 @@ "select-checkbox": "Selecionar", "tags-column": "Tags" }, + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "", + "title-folder": "" + }, "folder-actions-button": { "delete": "Excluir", "folder-actions": "Ações da pasta", @@ -159,6 +173,11 @@ "sub-text": "<0>Defina o texto que descreverá a correlação.", "title": "Definir rótulo de correlação (Passo 1 de 3)" }, + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "" + }, "list": { "delete": "excluir correlação", "label": "Etiqueta", @@ -355,6 +374,13 @@ "validation-required": "Precisa de um modelo JSON de painel de controle" } }, + "dashboard-links": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "title": "" + } + }, "dashboard-settings": { "annotations": { "title": "Anotações" @@ -405,6 +431,13 @@ "title": "Versões" } }, + "data-source-list": { + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "" + } + }, "data-source-picker": { "add-new-data-source": "Configurar uma nova fonte de dados", "built-in-list": { @@ -657,6 +690,10 @@ }, "add-widget": { "title": "Adicionar painel a partir da biblioteca de painéis" + }, + "empty-state": { + "message": "", + "more-info": "" } }, "library-panels": { @@ -1230,9 +1267,8 @@ "confirm-text": "Excluir" }, "empty": { - "button": "Criar lista de reprodução", - "pro-tip": "Você pode usar listas de reprodução para alternar entre painéis de controle na TVs sem controle de usuário", - "pro-tip-link-title": "Saiba mais", + "button": "", + "pro-tip": "", "title": "Não há listas de reprodução criadas ainda" } }, @@ -1346,6 +1382,10 @@ "orphaned-title": "<0>Painel de controle público órfão", "orphaned-tooltip": "O painel de controle vinculado já foi excluído" }, + "empty-state": { + "message": "", + "more-info": "" + }, "toggle": { "pause-sharing-toggle-text": "Pausar compartilhamento" } @@ -1450,7 +1490,10 @@ }, "service-accounts": { "empty-state": { - "message": "" + "button-title": "", + "message": "", + "more-info": "", + "title": "" } }, "share-modal": { @@ -1570,7 +1613,17 @@ }, "title": "Preferências" }, + "silences": { + "empty-state": { + "button-title": "", + "title": "" + } + }, "snapshot": { + "empty-state": { + "message": "", + "more-info": "" + }, "external-badge": "Externo", "name-column-header": "Nome", "url-column-header": "URL da captura", @@ -1583,7 +1636,10 @@ }, "teams": { "empty-state": { - "message": "" + "button-title": "", + "message": "", + "pro-tip": "", + "title": "" } }, "time-picker": { @@ -1705,5 +1761,13 @@ "textbox": { "placeholder": "Inserir valor da variável" } + }, + "variables": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "info-box-content-2": "", + "title": "" + } } } \ No newline at end of file diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 16453d4cefa..fd5b823ee48 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -25,6 +25,14 @@ "user": "用户" } }, + "annotations": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "info-box-content-2": "", + "title": "" + } + }, "api-keys": { "empty-state": { "message": "" @@ -67,6 +75,12 @@ "select-checkbox": "选择", "tags-column": "标签" }, + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "", + "title-folder": "" + }, "folder-actions-button": { "delete": "删除", "folder-actions": "文件夹操作", @@ -154,6 +168,11 @@ "sub-text": "<0>定义将会描述关联的文本。", "title": "定义关联标签(第 1 步,共 3 步)" }, + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "" + }, "list": { "delete": "删除关联", "label": "标签", @@ -350,6 +369,13 @@ "validation-required": "需要一个仪表板 JSON 模型" } }, + "dashboard-links": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "title": "" + } + }, "dashboard-settings": { "annotations": { "title": "注释" @@ -400,6 +426,13 @@ "title": "版本" } }, + "data-source-list": { + "empty-state": { + "button-title": "", + "pro-tip": "", + "title": "" + } + }, "data-source-picker": { "add-new-data-source": "配置新数据源", "built-in-list": { @@ -652,6 +685,10 @@ }, "add-widget": { "title": "从面板库中添加面板" + }, + "empty-state": { + "message": "", + "more-info": "" } }, "library-panels": { @@ -1224,9 +1261,8 @@ "confirm-text": "删除" }, "empty": { - "button": "创建播放列表", - "pro-tip": "您可以使用播放列表来循环电视上的仪表板而不受用户控制", - "pro-tip-link-title": "了解更多", + "button": "", + "pro-tip": "", "title": "尚未创建播放列表" } }, @@ -1340,6 +1376,10 @@ "orphaned-title": "<0>孤立的公共仪表板", "orphaned-tooltip": "链接的仪表板已被删除" }, + "empty-state": { + "message": "", + "more-info": "" + }, "toggle": { "pause-sharing-toggle-text": "暂停共享" } @@ -1444,7 +1484,10 @@ }, "service-accounts": { "empty-state": { - "message": "" + "button-title": "", + "message": "", + "more-info": "", + "title": "" } }, "share-modal": { @@ -1564,7 +1607,17 @@ }, "title": "首选项" }, + "silences": { + "empty-state": { + "button-title": "", + "title": "" + } + }, "snapshot": { + "empty-state": { + "message": "", + "more-info": "" + }, "external-badge": "外部", "name-column-header": "名称", "url-column-header": "快照网址", @@ -1577,7 +1630,10 @@ }, "teams": { "empty-state": { - "message": "" + "button-title": "", + "message": "", + "pro-tip": "", + "title": "" } }, "time-picker": { @@ -1699,5 +1755,13 @@ "textbox": { "placeholder": "输入变量值" } + }, + "variables": { + "empty-state": { + "button-title": "", + "info-box-content": "", + "info-box-content-2": "", + "title": "" + } } } \ No newline at end of file From dccad4e0811132e9d992e07faa4ad2048d50f2d3 Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Tue, 23 Apr 2024 06:52:26 +0200 Subject: [PATCH 027/222] Dashboard scenes: fix textbox value only set to first character of default value (#86595) Dashboard scene: fix textbox value only set to first character in default value --- .../serialization/transformSaveModelToScene.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index cdbcf461350..b89315a97af 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -392,9 +392,20 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode hide: variable.hide, }); } else if (variable.type === 'textbox') { + let val; + if (!variable?.current?.value) { + val = variable.query; + } else { + if (typeof variable.current.value === 'string') { + val = variable.current.value; + } else { + val = variable.current.value[0]; + } + } + return new TextBoxVariable({ ...commonProperties, - value: variable?.current?.value?.[0] ?? variable.query, + value: val, skipUrlSync: variable.skipUrlSync, hide: variable.hide, }); From 41617b174e3b74fb67a69b27e3fdb85336e6f701 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 07:28:32 +0100 Subject: [PATCH 028/222] Update dependency css-loader to v7 (#86711) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-ui/package.json | 2 +- .../grafana-pyroscope-datasource/package.json | 2 +- yarn.lock | 58 +++++++++++++------ 5 files changed, 45 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index b57829c2163..d47647de981 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "codeowners": "^5.1.1", "copy-webpack-plugin": "12.0.2", "core-js": "3.37.0", - "css-loader": "6.10.0", + "css-loader": "7.1.1", "css-minimizer-webpack-plugin": "6.0.0", "cypress": "13.1.0", "cypress-file-upload": "5.0.8", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index dc2299d2f41..c8092d9bde3 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -106,7 +106,7 @@ "@typescript-eslint/eslint-plugin": "6.21.0", "@typescript-eslint/parser": "6.21.0", "copy-webpack-plugin": "12.0.2", - "css-loader": "6.10.0", + "css-loader": "7.1.1", "esbuild": "0.18.12", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 1d0f7ef94c8..6ff8b3301f1 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -159,7 +159,7 @@ "@types/uuid": "9.0.8", "common-tags": "1.8.2", "core-js": "3.37.0", - "css-loader": "6.10.0", + "css-loader": "7.1.1", "csstype": "3.1.3", "esbuild": "0.18.12", "expose-loader": "5.0.0", diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index daf6dae7647..2c3bcb84dac 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -30,7 +30,7 @@ "@types/react": "18.2.79", "@types/react-dom": "18.2.25", "@types/testing-library__jest-dom": "5.14.9", - "css-loader": "6.10.0", + "css-loader": "7.1.1", "jest": "29.7.0", "style-loader": "3.3.4", "ts-node": "10.9.2", diff --git a/yarn.lock b/yarn.lock index a5265fb1f57..8893f76ddb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3357,7 +3357,7 @@ __metadata: "@types/react": "npm:18.2.79" "@types/react-dom": "npm:18.2.25" "@types/testing-library__jest-dom": "npm:5.14.9" - css-loader: "npm:6.10.0" + css-loader: "npm:7.1.1" fast-deep-equal: "npm:^3.1.3" jest: "npm:29.7.0" lodash: "npm:4.17.21" @@ -4043,7 +4043,7 @@ __metadata: "@typescript-eslint/eslint-plugin": "npm:6.21.0" "@typescript-eslint/parser": "npm:6.21.0" copy-webpack-plugin: "npm:12.0.2" - css-loader: "npm:6.10.0" + css-loader: "npm:7.1.1" d3: "npm:7.9.0" date-fns: "npm:3.6.0" debounce-promise: "npm:3.1.2" @@ -4343,7 +4343,7 @@ __metadata: classnames: "npm:2.5.1" common-tags: "npm:1.8.2" core-js: "npm:3.37.0" - css-loader: "npm:6.10.0" + css-loader: "npm:7.1.1" csstype: "npm:3.1.3" d3: "npm:7.9.0" date-fns: "npm:3.6.0" @@ -14270,7 +14270,31 @@ __metadata: languageName: node linkType: hard -"css-loader@npm:6.10.0, css-loader@npm:^6.7.1": +"css-loader@npm:7.1.1": + version: 7.1.1 + resolution: "css-loader@npm:7.1.1" + dependencies: + icss-utils: "npm:^5.1.0" + postcss: "npm:^8.4.33" + postcss-modules-extract-imports: "npm:^3.1.0" + postcss-modules-local-by-default: "npm:^4.0.5" + postcss-modules-scope: "npm:^3.2.0" + postcss-modules-values: "npm:^4.0.0" + postcss-value-parser: "npm:^4.2.0" + semver: "npm:^7.5.4" + peerDependencies: + "@rspack/core": 0.x || 1.x + webpack: ^5.27.0 + peerDependenciesMeta: + "@rspack/core": + optional: true + webpack: + optional: true + checksum: 10/435a21f19594f89e4d5da51f4d6d2de4d25d6f882117890875f6529e99fbe931ea258662fb680b70e7ccab2fd723084f2c3fff022c76d45c38893ae50ab6f08e + languageName: node + linkType: hard + +"css-loader@npm:^6.7.1": version: 6.10.0 resolution: "css-loader@npm:6.10.0" dependencies: @@ -18760,7 +18784,7 @@ __metadata: common-tags: "npm:1.8.2" copy-webpack-plugin: "npm:12.0.2" core-js: "npm:3.37.0" - css-loader: "npm:6.10.0" + css-loader: "npm:7.1.1" css-minimizer-webpack-plugin: "npm:6.0.0" cypress: "npm:13.1.0" cypress-file-upload: "npm:5.0.8" @@ -25250,36 +25274,36 @@ __metadata: languageName: node linkType: hard -"postcss-modules-extract-imports@npm:^3.0.0": - version: 3.0.0 - resolution: "postcss-modules-extract-imports@npm:3.0.0" +"postcss-modules-extract-imports@npm:^3.0.0, postcss-modules-extract-imports@npm:^3.1.0": + version: 3.1.0 + resolution: "postcss-modules-extract-imports@npm:3.1.0" peerDependencies: postcss: ^8.1.0 - checksum: 10/8d68bb735cef4d43f9cdc1053581e6c1c864860b77fcfb670372b39c5feeee018dc5ddb2be4b07fef9bcd601edded4262418bbaeaf1bd4af744446300cebe358 + checksum: 10/00bfd3aff045fc13ded8e3bbfd8dfc73eff9a9708db1b2a132266aef6544c8d2aee7a5d7e021885f6f9bbd5565a9a9ab52990316e21ad9468a2534f87df8e849 languageName: node linkType: hard -"postcss-modules-local-by-default@npm:^4.0.4": - version: 4.0.4 - resolution: "postcss-modules-local-by-default@npm:4.0.4" +"postcss-modules-local-by-default@npm:^4.0.4, postcss-modules-local-by-default@npm:^4.0.5": + version: 4.0.5 + resolution: "postcss-modules-local-by-default@npm:4.0.5" dependencies: icss-utils: "npm:^5.0.0" postcss-selector-parser: "npm:^6.0.2" postcss-value-parser: "npm:^4.1.0" peerDependencies: postcss: ^8.1.0 - checksum: 10/45790af417b2ed6ed26e9922724cf3502569995833a2489abcfc2bb44166096762825cc02f6132cc6a2fb235165e76b859f9d90e8a057bc188a1b2c17f2d7af0 + checksum: 10/b08b01aa7f3d1a80bb1a5508ba3a208578fdd2fb6e54e5613fac244a4e014aa7ca639a614859fec93b399e5a6f86938f7690ca60f7e57c4e35b75621d3c07734 languageName: node linkType: hard -"postcss-modules-scope@npm:^3.1.1": - version: 3.1.1 - resolution: "postcss-modules-scope@npm:3.1.1" +"postcss-modules-scope@npm:^3.1.1, postcss-modules-scope@npm:^3.2.0": + version: 3.2.0 + resolution: "postcss-modules-scope@npm:3.2.0" dependencies: postcss-selector-parser: "npm:^6.0.4" peerDependencies: postcss: ^8.1.0 - checksum: 10/ca035969eba62cf126864b10d7722e49c0d4f050cbd4618b6e9714d81b879cf4c53a5682501e00f9622e8f4ea6d7d7d53af295ae935fa833e0cc0bda416a287b + checksum: 10/17c293ad13355ba456498aa5815ddb7a4a736f7b781d89b294e1602a53b8d0e336131175f82460e290a0d672642f9039540042edc361d9000b682c44e766925b languageName: node linkType: hard From 51dcd1d9fdae4848fd313311fc4d9978e990431b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 08:59:28 +0200 Subject: [PATCH 029/222] Update dependency eslint-plugin-jest to v28 (#86745) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- yarn.lock | 98 +++--------------------- 3 files changed, 14 insertions(+), 88 deletions(-) diff --git a/package.json b/package.json index d47647de981..5cb6f7f423b 100644 --- a/package.json +++ b/package.json @@ -166,7 +166,7 @@ "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jest": "27.9.0", + "eslint-plugin-jest": "28.2.0", "eslint-plugin-jsdoc": "48.2.3", "eslint-plugin-jsx-a11y": "6.8.0", "eslint-plugin-lodash": "7.4.0", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index c8092d9bde3..e2807fbe1f2 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -111,7 +111,7 @@ "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jest": "27.9.0", + "eslint-plugin-jest": "28.2.0", "eslint-plugin-jsdoc": "48.2.3", "eslint-plugin-jsx-a11y": "6.8.0", "eslint-plugin-lodash": "7.4.0", diff --git a/yarn.lock b/yarn.lock index 8893f76ddb3..7c8c7f1a421 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4051,7 +4051,7 @@ __metadata: eslint: "npm:8.57.0" eslint-config-prettier: "npm:9.1.0" eslint-plugin-import: "npm:^2.26.0" - eslint-plugin-jest: "npm:27.9.0" + eslint-plugin-jest: "npm:28.2.0" eslint-plugin-jsdoc: "npm:48.2.3" eslint-plugin-jsx-a11y: "npm:6.8.0" eslint-plugin-lodash: "npm:7.4.0" @@ -10325,7 +10325,7 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:7.5.8, @types/semver@npm:^7.3.12, @types/semver@npm:^7.3.4, @types/semver@npm:^7.5.0": +"@types/semver@npm:7.5.8, @types/semver@npm:^7.3.4, @types/semver@npm:^7.5.0": version: 7.5.8 resolution: "@types/semver@npm:7.5.8" checksum: 10/3496808818ddb36deabfe4974fd343a78101fa242c4690044ccdc3b95dcf8785b494f5d628f2f47f38a702f8db9c53c67f47d7818f2be1b79f2efb09692e1178 @@ -10650,16 +10650,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/scope-manager@npm:5.59.9" - dependencies: - "@typescript-eslint/types": "npm:5.59.9" - "@typescript-eslint/visitor-keys": "npm:5.59.9" - checksum: 10/83b538212fc422cd6a26eee49deab60a29fa6d8bbd0dffca6daa02318959c76ddf1dc00db9ce0236258f26c1f726be78a25d2f6c5603233f591716d6299480e5 - languageName: node - linkType: hard - "@typescript-eslint/scope-manager@npm:6.18.1": version: 6.18.1 resolution: "@typescript-eslint/scope-manager@npm:6.18.1" @@ -10714,13 +10704,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/types@npm:5.59.9" - checksum: 10/49226e5384ac801db245fe668b4bd7610a11c5ade9c05ee93767fd188462c4d25755b8592f21210cc9856fae3c5566d4811ed0f7fefe30e48e5823e71ab4623e - languageName: node - linkType: hard - "@typescript-eslint/types@npm:6.18.1": version: 6.18.1 resolution: "@typescript-eslint/types@npm:6.18.1" @@ -10735,24 +10718,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/typescript-estree@npm:5.59.9" - dependencies: - "@typescript-eslint/types": "npm:5.59.9" - "@typescript-eslint/visitor-keys": "npm:5.59.9" - debug: "npm:^4.3.4" - globby: "npm:^11.1.0" - is-glob: "npm:^4.0.3" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10/79cf330815244f2ab12762df9296812c20f3ff859f14dc997a79ce09eabd7c8d0d190ed00fcdf380288a2b4035ca40c9f0002dc9c6c2875885ad3b94c2eab58b - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:6.18.1": version: 6.18.1 resolution: "@typescript-eslint/typescript-estree@npm:6.18.1" @@ -10825,34 +10790,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:^5.10.0": - version: 5.59.9 - resolution: "@typescript-eslint/utils@npm:5.59.9" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@types/json-schema": "npm:^7.0.9" - "@types/semver": "npm:^7.3.12" - "@typescript-eslint/scope-manager": "npm:5.59.9" - "@typescript-eslint/types": "npm:5.59.9" - "@typescript-eslint/typescript-estree": "npm:5.59.9" - eslint-scope: "npm:^5.1.1" - semver: "npm:^7.3.7" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10/e48429d9dd83d7ae1b95c64b35af790e36cd8c1b2b9b63b2f69b5f804bb58a12918396f2f0540afd413673e1e0d22399a2cd2e2ad6534e50af2990a04e8ca7c4 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:5.59.9": - version: 5.59.9 - resolution: "@typescript-eslint/visitor-keys@npm:5.59.9" - dependencies: - "@typescript-eslint/types": "npm:5.59.9" - eslint-visitor-keys: "npm:^3.3.0" - checksum: 10/85761ef0be6910cb4de841b3cd8f39a734f5373ed92f808365882ef357f0a33ad6f75c4b3bf0b408f0399781ac5d14f12033de3e4b53a46b61015444b05854c0 - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:6.18.1": version: 6.18.1 resolution: "@typescript-eslint/visitor-keys@npm:6.18.1" @@ -16684,21 +16621,21 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jest@npm:27.9.0": - version: 27.9.0 - resolution: "eslint-plugin-jest@npm:27.9.0" +"eslint-plugin-jest@npm:28.2.0": + version: 28.2.0 + resolution: "eslint-plugin-jest@npm:28.2.0" dependencies: - "@typescript-eslint/utils": "npm:^5.10.0" + "@typescript-eslint/utils": "npm:^6.0.0" peerDependencies: - "@typescript-eslint/eslint-plugin": ^5.0.0 || ^6.0.0 || ^7.0.0 - eslint: ^7.0.0 || ^8.0.0 + "@typescript-eslint/eslint-plugin": ^6.0.0 || ^7.0.0 + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 jest: "*" peerDependenciesMeta: "@typescript-eslint/eslint-plugin": optional: true jest: optional: true - checksum: 10/bca54347280c06c56516faea76042134dd74355c2de6c23361ba0e8736ecc01c62b144eea7eda7570ea4f4ee511c583bb8dab00d7153a1bd1740eb77b0038fd4 + checksum: 10/029a3d140a561d941580cbfee15ccacf4584971975f61111f07b87f01bf64c9739607cbe8e6fd3888429179ea8fd733e655ccd87b3b83b3b5cee2187e2355a4e languageName: node linkType: hard @@ -16849,7 +16786,7 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": +"eslint-scope@npm:5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" dependencies: @@ -18802,7 +18739,7 @@ __metadata: eslint: "npm:8.57.0" eslint-config-prettier: "npm:9.1.0" eslint-plugin-import: "npm:^2.26.0" - eslint-plugin-jest: "npm:27.9.0" + eslint-plugin-jest: "npm:28.2.0" eslint-plugin-jsdoc: "npm:48.2.3" eslint-plugin-jsx-a11y: "npm:6.8.0" eslint-plugin-lodash: "npm:7.4.0" @@ -30579,24 +30516,13 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.10.0, tslib@npm:^1.13.0, tslib@npm:^1.8.1": +"tslib@npm:^1.10.0, tslib@npm:^1.13.0": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb languageName: node linkType: hard -"tsutils@npm:^3.21.0": - version: 3.21.0 - resolution: "tsutils@npm:3.21.0" - dependencies: - tslib: "npm:^1.8.1" - peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - checksum: 10/ea036bec1dd024e309939ffd49fda7a351c0e87a1b8eb049570dd119d447250e2c56e0e6c00554e8205760e7417793fdebff752a46e573fbe07d4f375502a5b2 - languageName: node - linkType: hard - "tuf-js@npm:^1.1.7": version: 1.1.7 resolution: "tuf-js@npm:1.1.7" From 4d9e35ba57573e564e022da82164b0880e67518d Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Tue, 23 Apr 2024 10:00:43 +0300 Subject: [PATCH 030/222] SSO: add configurableProviders list to SSO service (#86622) * add configurableProviders list to sso service * address feedback --- .../ssosettings/ssosettingsimpl/service.go | 43 ++++++----- .../ssosettingsimpl/service_test.go | 71 ++++++++++--------- 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/pkg/services/ssosettings/ssosettingsimpl/service.go b/pkg/services/ssosettings/ssosettingsimpl/service.go index fbc6e06c3cd..38862651e74 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service.go @@ -8,6 +8,8 @@ import ( "strings" "time" + "github.com/prometheus/client_golang/prometheus" + "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" @@ -24,7 +26,6 @@ import ( "github.com/grafana/grafana/pkg/services/ssosettings/models" "github.com/grafana/grafana/pkg/services/ssosettings/strategies" "github.com/grafana/grafana/pkg/setting" - "github.com/prometheus/client_golang/prometheus" ) var _ ssosettings.Service = (*Service)(nil) @@ -37,9 +38,10 @@ type Service struct { secrets secrets.Service metrics *metrics - fbStrategies []ssosettings.FallbackStrategy - providersList []string - reloadables map[string]ssosettings.Reloadable + fbStrategies []ssosettings.FallbackStrategy + providersList []string + configurableProviders map[string]bool + reloadables map[string]ssosettings.Reloadable } func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl, @@ -50,27 +52,34 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl, strategies.NewOAuthStrategy(cfg), } + configurableProviders := make(map[string]bool) + for provider, enabled := range cfg.SSOSettingsConfigurableProviders { + configurableProviders[provider] = enabled + } + providersList := ssosettings.AllOAuthProviders if licensing.FeatureEnabled(social.SAMLProviderName) { fbStrategies = append(fbStrategies, strategies.NewSAMLStrategy(settingsProvider)) - if cfg.SSOSettingsConfigurableProviders[social.SAMLProviderName] { + if features.IsEnabledGlobally(featuremgmt.FlagSsoSettingsSAML) { providersList = append(providersList, social.SAMLProviderName) + configurableProviders[social.SAMLProviderName] = true } } store := database.ProvideStore(sqlStore) svc := &Service{ - logger: log.New("ssosettings.service"), - cfg: cfg, - store: store, - ac: ac, - fbStrategies: fbStrategies, - secrets: secrets, - metrics: newMetrics(registerer), - providersList: providersList, - reloadables: make(map[string]ssosettings.Reloadable), + logger: log.New("ssosettings.service"), + cfg: cfg, + store: store, + ac: ac, + fbStrategies: fbStrategies, + secrets: secrets, + metrics: newMetrics(registerer), + providersList: providersList, + configurableProviders: configurableProviders, + reloadables: make(map[string]ssosettings.Reloadable), } usageStats.RegisterMetricsFunc(svc.getUsageStats) @@ -160,7 +169,7 @@ func (s *Service) ListWithRedactedSecrets(ctx context.Context) ([]*models.SSOSet return nil, err } - configurableSettings := make([]*models.SSOSettings, 0, len(s.cfg.SSOSettingsConfigurableProviders)) + configurableSettings := make([]*models.SSOSettings, 0, len(s.configurableProviders)) for _, provider := range storeSettings { if s.isProviderConfigurable(provider.Provider) { configurableSettings = append(configurableSettings, provider) @@ -431,8 +440,8 @@ func (s *Service) decryptSecrets(ctx context.Context, settings map[string]any) ( } func (s *Service) isProviderConfigurable(provider string) bool { - _, ok := s.cfg.SSOSettingsConfigurableProviders[provider] - return ok + enabled, ok := s.configurableProviders[provider] + return ok && enabled } // removeSecrets removes all the secrets from the map and replaces them with a redacted password diff --git a/pkg/services/ssosettings/ssosettingsimpl/service_test.go b/pkg/services/ssosettings/ssosettingsimpl/service_test.go index 8d576ce06f4..52c2ae69027 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service_test.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service_test.go @@ -241,7 +241,7 @@ func TestService_GetForProvider(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) if tc.setup != nil { tc.setup(env) } @@ -350,7 +350,7 @@ func TestService_GetForProviderWithRedactedSecrets(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) if tc.setup != nil { tc.setup(env) } @@ -501,7 +501,7 @@ func TestService_List(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) if tc.setup != nil { tc.setup(env) } @@ -803,7 +803,7 @@ func TestService_ListWithRedactedSecrets(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) if tc.setup != nil { tc.setup(env) } @@ -827,7 +827,7 @@ func TestService_Upsert(t *testing.T) { t.Run("successfully upsert SSO settings", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.AzureADProviderName settings := models.SSOSettings{ @@ -890,7 +890,7 @@ func TestService_Upsert(t *testing.T) { t.Run("returns error if provider is not configurable", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.GrafanaComProviderName settings := &models.SSOSettings{ @@ -913,7 +913,7 @@ func TestService_Upsert(t *testing.T) { t.Run("returns error if provider was not found in reloadables", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.AzureADProviderName settings := &models.SSOSettings{ @@ -937,7 +937,7 @@ func TestService_Upsert(t *testing.T) { t.Run("returns error if validation fails", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.AzureADProviderName settings := models.SSOSettings{ @@ -961,7 +961,7 @@ func TestService_Upsert(t *testing.T) { t.Run("returns error if a fallback strategy is not available for the provider", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) settings := &models.SSOSettings{ Provider: social.AzureADProviderName, @@ -982,7 +982,7 @@ func TestService_Upsert(t *testing.T) { t.Run("returns error if secrets encryption failed", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.OktaProviderName settings := models.SSOSettings{ @@ -1007,7 +1007,7 @@ func TestService_Upsert(t *testing.T) { t.Run("should not update the current secret if the secret has not been updated", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.AzureADProviderName settings := models.SSOSettings{ @@ -1044,7 +1044,7 @@ func TestService_Upsert(t *testing.T) { t.Run("returns error if store failed to upsert settings", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.AzureADProviderName settings := models.SSOSettings{ @@ -1076,7 +1076,7 @@ func TestService_Upsert(t *testing.T) { t.Run("successfully upsert SSO settings if reload fails", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.AzureADProviderName settings := models.SSOSettings{ @@ -1109,7 +1109,7 @@ func TestService_Delete(t *testing.T) { t.Run("successfully delete SSO settings", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) var wg sync.WaitGroup wg.Add(1) @@ -1147,7 +1147,7 @@ func TestService_Delete(t *testing.T) { t.Run("return error if SSO setting was not found for the specified provider", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.AzureADProviderName reloadable := ssosettingstests.NewMockReloadable(t) @@ -1163,7 +1163,7 @@ func TestService_Delete(t *testing.T) { t.Run("should not delete the SSO settings if the provider is not configurable", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) env.cfg.SSOSettingsConfigurableProviders = map[string]bool{social.AzureADProviderName: true} provider := social.GrafanaComProviderName @@ -1176,7 +1176,7 @@ func TestService_Delete(t *testing.T) { t.Run("return error when store fails to delete the SSO settings for the specified provider", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.AzureADProviderName env.store.ExpectedError = errors.New("delete sso settings failed") @@ -1189,7 +1189,7 @@ func TestService_Delete(t *testing.T) { t.Run("return successfully when the deletion was successful but reloading the settings fail", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.AzureADProviderName reloadable := ssosettingstests.NewMockReloadable(t) @@ -1211,7 +1211,7 @@ func TestService_DoReload(t *testing.T) { t.Run("successfully reload settings", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) settingsList := []*models.SSOSettings{ { @@ -1251,7 +1251,7 @@ func TestService_DoReload(t *testing.T) { t.Run("successfully reload settings when some providers have empty settings", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) settingsList := []*models.SSOSettings{ { @@ -1281,7 +1281,7 @@ func TestService_DoReload(t *testing.T) { t.Run("failed fetching the SSO settings", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := "github" @@ -1382,7 +1382,7 @@ func TestService_decryptSecrets(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) if tc.setup != nil { tc.setup(env) @@ -1407,12 +1407,12 @@ func Test_ProviderService(t *testing.T) { tests := []struct { name string isLicenseEnabled bool - configurableProviders map[string]bool + samlEnabled bool expectedProvidersList []string strategiesLength int }{ { - name: "should return all OAuth providers but not saml because the licensing feature is not enabled", + name: "should return all OAuth providers but not SAML because the licensing feature is not enabled", isLicenseEnabled: false, expectedProvidersList: []string{ "github", @@ -1426,7 +1426,7 @@ func Test_ProviderService(t *testing.T) { strategiesLength: 1, }, { - name: "should return all fallback strategies and it should return all OAuth providers but not saml because the licensing feature is enabled but the configurable provider is not setup", + name: "should return all fallback strategies and it should return all OAuth providers but not SAML because the licensing feature is enabled but the configurable provider is not setup", isLicenseEnabled: true, expectedProvidersList: []string{ "github", @@ -1440,9 +1440,9 @@ func Test_ProviderService(t *testing.T) { strategiesLength: 2, }, { - name: "should return all fallback strategies and it should return all OAuth providers and saml because the licensing feature is enabled and the provider is setup", - isLicenseEnabled: true, - configurableProviders: map[string]bool{"saml": true}, + name: "should return all fallback strategies and it should return all OAuth providers and SAML because the licensing feature is enabled and the provider is setup", + isLicenseEnabled: true, + samlEnabled: true, expectedProvidersList: []string{ "github", "gitlab", @@ -1461,7 +1461,7 @@ func Test_ProviderService(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, tc.isLicenseEnabled, true, tc.configurableProviders) + env := setupTestEnv(t, tc.isLicenseEnabled, true, tc.samlEnabled) require.Equal(t, tc.expectedProvidersList, env.service.providersList) require.Equal(t, tc.strategiesLength, len(env.service.fbStrategies)) @@ -1469,7 +1469,7 @@ func Test_ProviderService(t *testing.T) { } } -func setupTestEnv(t *testing.T, isLicensingEnabled, keepFallbackStratergies bool, extraConfigurableProviders map[string]bool) testEnv { +func setupTestEnv(t *testing.T, isLicensingEnabled, keepFallbackStratergies, samlEnabled bool) testEnv { t.Helper() store := ssosettingstests.NewFakeStore() @@ -1491,10 +1491,6 @@ func setupTestEnv(t *testing.T, isLicensingEnabled, keepFallbackStratergies bool "gitlab": true, } - for k, v := range extraConfigurableProviders { - configurableProviders[k] = v - } - cfg := &setting.Cfg{ SSOSettingsConfigurableProviders: configurableProviders, Raw: iniFile, @@ -1503,12 +1499,17 @@ func setupTestEnv(t *testing.T, isLicensingEnabled, keepFallbackStratergies bool licensing := licensingtest.NewFakeLicensing() licensing.On("FeatureEnabled", "saml").Return(isLicensingEnabled) + featureManager := featuremgmt.WithManager() + if samlEnabled { + featureManager = featuremgmt.WithManager(featuremgmt.FlagSsoSettingsSAML) + } + svc := ProvideService( cfg, &dbtest.FakeDB{}, accessControl, routing.NewRouteRegister(), - featuremgmt.WithManager(nil), + featureManager, secretsFakes.NewMockService(t), &usagestats.UsageStatsMock{}, prometheus.NewRegistry(), From 579cf9bd7dc5cb43a2421e1180536f098b8cfd8b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 08:26:02 +0100 Subject: [PATCH 031/222] Update dependency esbuild to v0.20.2 (#76342) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-data/package.json | 2 +- packages/grafana-e2e-selectors/package.json | 2 +- packages/grafana-icons/package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-runtime/package.json | 2 +- packages/grafana-schema/package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 208 ++++++++++---------- 9 files changed, 112 insertions(+), 112 deletions(-) diff --git a/package.json b/package.json index 5cb6f7f423b..21630f3c768 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,7 @@ "css-minimizer-webpack-plugin": "6.0.0", "cypress": "13.1.0", "cypress-file-upload": "5.0.8", - "esbuild": "0.20.1", + "esbuild": "0.20.2", "esbuild-loader": "4.1.0", "esbuild-plugin-browserslist": "^0.11.0", "eslint": "8.57.0", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 25a26d743b0..11f2c7e58c2 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -71,7 +71,7 @@ "@types/react": "18.2.79", "@types/react-dom": "18.2.25", "@types/tinycolor2": "1.4.6", - "esbuild": "0.18.12", + "esbuild": "0.20.2", "react": "18.2.0", "react-dom": "18.2.0", "rimraf": "5.0.5", diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index ae0a68fd669..035b22c0a85 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@rollup/plugin-node-resolve": "15.2.3", "@types/node": "20.12.7", - "esbuild": "0.18.12", + "esbuild": "0.20.2", "rimraf": "5.0.5", "rollup": "2.79.1", "rollup-plugin-dts": "^5.0.0", diff --git a/packages/grafana-icons/package.json b/packages/grafana-icons/package.json index f2a8dcee0f3..76c87805587 100644 --- a/packages/grafana-icons/package.json +++ b/packages/grafana-icons/package.json @@ -45,7 +45,7 @@ "@types/node": "20.12.7", "@types/react": "18.2.79", "@types/react-dom": "18.2.25", - "esbuild": "0.18.12", + "esbuild": "0.20.2", "prettier": "3.2.5", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index e2807fbe1f2..411317c70e7 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -107,7 +107,7 @@ "@typescript-eslint/parser": "6.21.0", "copy-webpack-plugin": "12.0.2", "css-loader": "7.1.1", - "esbuild": "0.18.12", + "esbuild": "0.20.2", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "^2.26.0", diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index 0b40a487e4a..e5c488ff35a 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -61,7 +61,7 @@ "@types/react": "18.2.79", "@types/react-dom": "18.2.25", "@types/systemjs": "6.13.5", - "esbuild": "0.18.12", + "esbuild": "0.20.2", "lodash": "4.17.21", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index dacc87e3583..138a11501be 100644 --- a/packages/grafana-schema/package.json +++ b/packages/grafana-schema/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@grafana/tsconfig": "^1.3.0-rc1", "@rollup/plugin-node-resolve": "15.2.3", - "esbuild": "0.18.12", + "esbuild": "0.20.2", "glob": "^10.2.7", "rimraf": "5.0.5", "rollup": "2.79.1", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 6ff8b3301f1..c7aacf39f39 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -161,7 +161,7 @@ "core-js": "3.37.0", "css-loader": "7.1.1", "csstype": "3.1.3", - "esbuild": "0.18.12", + "esbuild": "0.20.2", "expose-loader": "5.0.0", "mock-raf": "1.0.1", "process": "^0.11.10", diff --git a/yarn.lock b/yarn.lock index 7c8c7f1a421..c4c87b1d3f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2386,9 +2386,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/aix-ppc64@npm:0.20.1" +"@esbuild/aix-ppc64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/aix-ppc64@npm:0.20.2" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard @@ -2414,9 +2414,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/android-arm64@npm:0.20.1" +"@esbuild/android-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/android-arm64@npm:0.20.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -2442,9 +2442,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/android-arm@npm:0.20.1" +"@esbuild/android-arm@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/android-arm@npm:0.20.2" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -2470,9 +2470,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/android-x64@npm:0.20.1" +"@esbuild/android-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/android-x64@npm:0.20.2" conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -2498,9 +2498,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/darwin-arm64@npm:0.20.1" +"@esbuild/darwin-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/darwin-arm64@npm:0.20.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -2526,9 +2526,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/darwin-x64@npm:0.20.1" +"@esbuild/darwin-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/darwin-x64@npm:0.20.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -2554,9 +2554,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/freebsd-arm64@npm:0.20.1" +"@esbuild/freebsd-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/freebsd-arm64@npm:0.20.2" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -2582,9 +2582,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/freebsd-x64@npm:0.20.1" +"@esbuild/freebsd-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/freebsd-x64@npm:0.20.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -2610,9 +2610,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/linux-arm64@npm:0.20.1" +"@esbuild/linux-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-arm64@npm:0.20.2" conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -2638,9 +2638,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/linux-arm@npm:0.20.1" +"@esbuild/linux-arm@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-arm@npm:0.20.2" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -2666,9 +2666,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/linux-ia32@npm:0.20.1" +"@esbuild/linux-ia32@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-ia32@npm:0.20.2" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -2694,9 +2694,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/linux-loong64@npm:0.20.1" +"@esbuild/linux-loong64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-loong64@npm:0.20.2" conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -2722,9 +2722,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/linux-mips64el@npm:0.20.1" +"@esbuild/linux-mips64el@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-mips64el@npm:0.20.2" conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -2750,9 +2750,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/linux-ppc64@npm:0.20.1" +"@esbuild/linux-ppc64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-ppc64@npm:0.20.2" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -2778,9 +2778,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/linux-riscv64@npm:0.20.1" +"@esbuild/linux-riscv64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-riscv64@npm:0.20.2" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -2806,9 +2806,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/linux-s390x@npm:0.20.1" +"@esbuild/linux-s390x@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-s390x@npm:0.20.2" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -2834,9 +2834,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/linux-x64@npm:0.20.1" +"@esbuild/linux-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-x64@npm:0.20.2" conditions: os=linux & cpu=x64 languageName: node linkType: hard @@ -2862,9 +2862,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/netbsd-x64@npm:0.20.1" +"@esbuild/netbsd-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/netbsd-x64@npm:0.20.2" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard @@ -2890,9 +2890,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/openbsd-x64@npm:0.20.1" +"@esbuild/openbsd-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/openbsd-x64@npm:0.20.2" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard @@ -2918,9 +2918,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/sunos-x64@npm:0.20.1" +"@esbuild/sunos-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/sunos-x64@npm:0.20.2" conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -2946,9 +2946,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/win32-arm64@npm:0.20.1" +"@esbuild/win32-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/win32-arm64@npm:0.20.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -2974,9 +2974,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/win32-ia32@npm:0.20.1" +"@esbuild/win32-ia32@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/win32-ia32@npm:0.20.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -3002,9 +3002,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.20.1": - version: 0.20.1 - resolution: "@esbuild/win32-x64@npm:0.20.1" +"@esbuild/win32-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/win32-x64@npm:0.20.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -3643,7 +3643,7 @@ __metadata: d3-interpolate: "npm:3.0.1" date-fns: "npm:3.6.0" dompurify: "npm:^3.0.0" - esbuild: "npm:0.18.12" + esbuild: "npm:0.20.2" eventemitter3: "npm:5.0.1" fast_array_intersect: "npm:1.1.0" history: "npm:4.10.1" @@ -3693,7 +3693,7 @@ __metadata: "@grafana/tsconfig": "npm:^1.3.0-rc1" "@rollup/plugin-node-resolve": "npm:15.2.3" "@types/node": "npm:20.12.7" - esbuild: "npm:0.18.12" + esbuild: "npm:0.20.2" rimraf: "npm:5.0.5" rollup: "npm:2.79.1" rollup-plugin-dts: "npm:^5.0.0" @@ -4047,7 +4047,7 @@ __metadata: d3: "npm:7.9.0" date-fns: "npm:3.6.0" debounce-promise: "npm:3.1.2" - esbuild: "npm:0.18.12" + esbuild: "npm:0.20.2" eslint: "npm:8.57.0" eslint-config-prettier: "npm:9.1.0" eslint-plugin-import: "npm:^2.26.0" @@ -4128,7 +4128,7 @@ __metadata: "@types/react": "npm:18.2.79" "@types/react-dom": "npm:18.2.25" "@types/systemjs": "npm:6.13.5" - esbuild: "npm:0.18.12" + esbuild: "npm:0.20.2" history: "npm:4.10.1" lodash: "npm:4.17.21" react: "npm:18.2.0" @@ -4163,7 +4163,7 @@ __metadata: "@types/node": "npm:20.12.7" "@types/react": "npm:18.2.79" "@types/react-dom": "npm:18.2.25" - esbuild: "npm:0.18.12" + esbuild: "npm:0.20.2" prettier: "npm:3.2.5" react: "npm:18.2.0" react-dom: "npm:18.2.0" @@ -4206,7 +4206,7 @@ __metadata: dependencies: "@grafana/tsconfig": "npm:^1.3.0-rc1" "@rollup/plugin-node-resolve": "npm:15.2.3" - esbuild: "npm:0.18.12" + esbuild: "npm:0.20.2" glob: "npm:^10.2.7" rimraf: "npm:5.0.5" rollup: "npm:2.79.1" @@ -4347,7 +4347,7 @@ __metadata: csstype: "npm:3.1.3" d3: "npm:7.9.0" date-fns: "npm:3.6.0" - esbuild: "npm:0.18.12" + esbuild: "npm:0.20.2" expose-loader: "npm:5.0.0" hoist-non-react-statics: "npm:3.3.2" i18next: "npm:^23.0.0" @@ -16262,33 +16262,33 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:0.20.1, esbuild@npm:^0.20.0, esbuild@npm:^0.20.1": - version: 0.20.1 - resolution: "esbuild@npm:0.20.1" +"esbuild@npm:0.20.2, esbuild@npm:^0.20.0, esbuild@npm:^0.20.1": + version: 0.20.2 + resolution: "esbuild@npm:0.20.2" dependencies: - "@esbuild/aix-ppc64": "npm:0.20.1" - "@esbuild/android-arm": "npm:0.20.1" - "@esbuild/android-arm64": "npm:0.20.1" - "@esbuild/android-x64": "npm:0.20.1" - "@esbuild/darwin-arm64": "npm:0.20.1" - "@esbuild/darwin-x64": "npm:0.20.1" - "@esbuild/freebsd-arm64": "npm:0.20.1" - "@esbuild/freebsd-x64": "npm:0.20.1" - "@esbuild/linux-arm": "npm:0.20.1" - "@esbuild/linux-arm64": "npm:0.20.1" - "@esbuild/linux-ia32": "npm:0.20.1" - "@esbuild/linux-loong64": "npm:0.20.1" - "@esbuild/linux-mips64el": "npm:0.20.1" - "@esbuild/linux-ppc64": "npm:0.20.1" - "@esbuild/linux-riscv64": "npm:0.20.1" - "@esbuild/linux-s390x": "npm:0.20.1" - "@esbuild/linux-x64": "npm:0.20.1" - "@esbuild/netbsd-x64": "npm:0.20.1" - "@esbuild/openbsd-x64": "npm:0.20.1" - "@esbuild/sunos-x64": "npm:0.20.1" - "@esbuild/win32-arm64": "npm:0.20.1" - "@esbuild/win32-ia32": "npm:0.20.1" - "@esbuild/win32-x64": "npm:0.20.1" + "@esbuild/aix-ppc64": "npm:0.20.2" + "@esbuild/android-arm": "npm:0.20.2" + "@esbuild/android-arm64": "npm:0.20.2" + "@esbuild/android-x64": "npm:0.20.2" + "@esbuild/darwin-arm64": "npm:0.20.2" + "@esbuild/darwin-x64": "npm:0.20.2" + "@esbuild/freebsd-arm64": "npm:0.20.2" + "@esbuild/freebsd-x64": "npm:0.20.2" + "@esbuild/linux-arm": "npm:0.20.2" + "@esbuild/linux-arm64": "npm:0.20.2" + "@esbuild/linux-ia32": "npm:0.20.2" + "@esbuild/linux-loong64": "npm:0.20.2" + "@esbuild/linux-mips64el": "npm:0.20.2" + "@esbuild/linux-ppc64": "npm:0.20.2" + "@esbuild/linux-riscv64": "npm:0.20.2" + "@esbuild/linux-s390x": "npm:0.20.2" + "@esbuild/linux-x64": "npm:0.20.2" + "@esbuild/netbsd-x64": "npm:0.20.2" + "@esbuild/openbsd-x64": "npm:0.20.2" + "@esbuild/sunos-x64": "npm:0.20.2" + "@esbuild/win32-arm64": "npm:0.20.2" + "@esbuild/win32-ia32": "npm:0.20.2" + "@esbuild/win32-x64": "npm:0.20.2" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -16338,7 +16338,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/b672fd5df28ae917e2b16e77edbbf6b3099c390ab0a9d4cd331f78b4a4567cf33f506a055e1aa272ac90f7f522835b2173abea9bac6c38906acfda68e60a7ab7 + checksum: 10/663215ab7e599651e00d61b528a63136e1f1d397db8b9c3712540af928c9476d61da95aefa81b7a8dfc7a9fdd7616fcf08395c27be68be8c99953fb461863ce4 languageName: node linkType: hard @@ -18733,7 +18733,7 @@ __metadata: debounce-promise: "npm:3.1.2" diff: "npm:^5.1.0" emotion: "npm:11.0.0" - esbuild: "npm:0.20.1" + esbuild: "npm:0.20.2" esbuild-loader: "npm:4.1.0" esbuild-plugin-browserslist: "npm:^0.11.0" eslint: "npm:8.57.0" From b224be5940dc3e1dd3ab5acf053220b18d726548 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 08:50:02 +0100 Subject: [PATCH 032/222] Update dependency @grafana/scenes to v4.12.0 (#86747) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c4c87b1d3f2..b18b011dfbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4181,8 +4181,8 @@ __metadata: linkType: soft "@grafana/scenes@npm:^4.10.0": - version: 4.11.3 - resolution: "@grafana/scenes@npm:4.11.3" + version: 4.12.0 + resolution: "@grafana/scenes@npm:4.12.0" dependencies: "@grafana/e2e-selectors": "npm:10.3.3" react-grid-layout: "npm:1.3.4" @@ -4196,7 +4196,7 @@ __metadata: "@grafana/ui": ^10.0.3 react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/a86f60be983f575853ce1a444b798671a069ee4b116f0f2c164c56651bb197ffbe511c39d5739aaa246ddeb7d576f8daceb73f0fbcfd8c809ff6b5c352c651b1 + checksum: 10/d59564176f432e947d88e1c25dc901dd424aa61b4a0fa91b5f30704ec1da698b0c67b3c0a79caf112df83a0b84b583a8732d157a7eec87cd43cdbb4949007d3f languageName: node linkType: hard From 6c777519d8ccb18cb00593c0d14af416a4fe9bec Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 09:08:46 +0100 Subject: [PATCH 033/222] Update dependency style-loader to v4 (#86749) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-ui/package.json | 2 +- .../grafana-pyroscope-datasource/package.json | 2 +- yarn.lock | 19 ++++++++++++++----- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 21630f3c768..1598365ea59 100644 --- a/package.json +++ b/package.json @@ -211,7 +211,7 @@ "rudder-sdk-js": "2.48.6", "sass": "1.75.0", "sass-loader": "14.2.1", - "style-loader": "3.3.4", + "style-loader": "4.0.0", "stylelint": "16.3.1", "stylelint-config-sass-guidelines": "11.1.0", "terser-webpack-plugin": "5.3.10", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 411317c70e7..29fa45e6ad6 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -134,7 +134,7 @@ "rollup-plugin-node-externals": "^5.0.0", "sass": "1.75.0", "sass-loader": "14.2.1", - "style-loader": "3.3.4", + "style-loader": "4.0.0", "testing-library-selector": "0.3.1", "ts-node": "10.9.2", "typescript": "5.4.5", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index c7aacf39f39..83be95aeafa 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -180,7 +180,7 @@ "storybook": "7.4.5", "storybook-addon-turbo-build": "2.0.1", "storybook-dark-mode": "3.0.1", - "style-loader": "3.3.4", + "style-loader": "4.0.0", "typescript": "5.4.5", "webpack": "5.91.0" }, diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index 2c3bcb84dac..cf753d2c504 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -32,7 +32,7 @@ "@types/testing-library__jest-dom": "5.14.9", "css-loader": "7.1.1", "jest": "29.7.0", - "style-loader": "3.3.4", + "style-loader": "4.0.0", "ts-node": "10.9.2", "typescript": "5.4.5", "webpack": "5.91.0" diff --git a/yarn.lock b/yarn.lock index b18b011dfbc..3661769da8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3367,7 +3367,7 @@ __metadata: react-dom: "npm:18.2.0" react-use: "npm:17.5.0" rxjs: "npm:7.8.1" - style-loader: "npm:3.3.4" + style-loader: "npm:4.0.0" ts-node: "npm:10.9.2" tslib: "npm:2.6.2" typescript: "npm:5.4.5" @@ -4091,7 +4091,7 @@ __metadata: sass: "npm:1.75.0" sass-loader: "npm:14.2.1" semver: "npm:7.6.0" - style-loader: "npm:3.3.4" + style-loader: "npm:4.0.0" testing-library-selector: "npm:0.3.1" ts-node: "npm:10.9.2" tslib: "npm:2.6.2" @@ -4403,7 +4403,7 @@ __metadata: storybook: "npm:7.4.5" storybook-addon-turbo-build: "npm:2.0.1" storybook-dark-mode: "npm:3.0.1" - style-loader: "npm:3.3.4" + style-loader: "npm:4.0.0" tinycolor2: "npm:1.6.0" tslib: "npm:2.6.2" typescript: "npm:5.4.5" @@ -18873,7 +18873,7 @@ __metadata: slate: "npm:0.47.9" slate-plain-serializer: "npm:0.7.13" slate-react: "npm:0.22.10" - style-loader: "npm:3.3.4" + style-loader: "npm:4.0.0" stylelint: "npm:16.3.1" stylelint-config-sass-guidelines: "npm:11.1.0" symbol-observable: "npm:4.0.0" @@ -29602,7 +29602,16 @@ __metadata: languageName: node linkType: hard -"style-loader@npm:3.3.4, style-loader@npm:^3.3.1": +"style-loader@npm:4.0.0": + version: 4.0.0 + resolution: "style-loader@npm:4.0.0" + peerDependencies: + webpack: ^5.27.0 + checksum: 10/93f25b7e70cfca9d1d8427170384262b59a5b0e84e7191a5a26636a77799caeed46d9a3e45ee7b9afa0f69176e3b98d5a6c5e81593ff1fd0946f1c5682fd2a68 + languageName: node + linkType: hard + +"style-loader@npm:^3.3.1": version: 3.3.4 resolution: "style-loader@npm:3.3.4" peerDependencies: From 99cbb5281cd969b61fd4b5471d3a6c25b17b807a Mon Sep 17 00:00:00 2001 From: Thomas Wikman Date: Tue, 23 Apr 2024 10:09:08 +0200 Subject: [PATCH 034/222] Grafana UI: Add timezone selector to Storybook toolbar (#86703) * Add timezone selector to Storybook toolbar So that it's possible to preview how components that are using date functions, behave when timezone differs from browser/client. * Re-order imports --- packages/grafana-ui/.storybook/preview.ts | 21 +++++++++++++++++-- .../src/utils/storybook/withTimeZone.tsx | 12 +++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 packages/grafana-ui/src/utils/storybook/withTimeZone.tsx diff --git a/packages/grafana-ui/.storybook/preview.ts b/packages/grafana-ui/.storybook/preview.ts index e58cfed7230..1e7e84f797c 100644 --- a/packages/grafana-ui/.storybook/preview.ts +++ b/packages/grafana-ui/.storybook/preview.ts @@ -1,5 +1,6 @@ import { Preview } from '@storybook/react'; import 'jquery'; +import { getTimeZone, getTimeZones } from '@grafana/data'; import '../../../public/vendor/flot/jquery.flot.js'; import '../../../public/vendor/flot/jquery.flot.selection'; @@ -12,13 +13,14 @@ import '../../../public/vendor/flot/jquery.flot.dashes'; import '../../../public/vendor/flot/jquery.flot.gauge'; import { withTheme } from '../src/utils/storybook/withTheme'; +import { withTimeZone } from '../src/utils/storybook/withTimeZone'; import { ThemedDocsContainer } from '../src/utils/storybook/ThemedDocsContainer'; // @ts-ignore import lightTheme from './grafana.light.scss'; // @ts-ignore import darkTheme from './grafana.dark.scss'; -import { GrafanaLight, GrafanaDark } from './storybookTheme'; +import { GrafanaDark, GrafanaLight } from './storybookTheme'; const handleThemeChange = (theme: any) => { if (theme !== 'light') { @@ -31,7 +33,7 @@ const handleThemeChange = (theme: any) => { }; const preview: Preview = { - decorators: [withTheme(handleThemeChange)], + decorators: [withTheme(handleThemeChange), withTimeZone()], parameters: { actions: { argTypesRegex: '^on[A-Z].*' }, darkMode: { @@ -66,6 +68,21 @@ const preview: Preview = { }, }, }, + globalTypes: { + timeZone: { + description: 'Set the timezone for the storybook preview', + defaultValue: getTimeZone(), + toolbar: { + icon: 'globe', + items: getTimeZones(true) + .filter((timezone) => !!timezone) + .map((timezone) => ({ + title: timezone, + value: timezone, + })), + }, + }, + }, }; export default preview; diff --git a/packages/grafana-ui/src/utils/storybook/withTimeZone.tsx b/packages/grafana-ui/src/utils/storybook/withTimeZone.tsx new file mode 100644 index 00000000000..27445b3c24e --- /dev/null +++ b/packages/grafana-ui/src/utils/storybook/withTimeZone.tsx @@ -0,0 +1,12 @@ +import { Decorator } from '@storybook/react'; +import { useEffect } from 'react'; + +import { setTimeZoneResolver } from '@grafana/data'; + +export const withTimeZone = (): Decorator => (Story, context) => { + useEffect(() => { + setTimeZoneResolver(() => context.globals.timeZone ?? 'browser'); + }, [context.globals.timeZone]); + + return Story(); +}; From 2d9d0e61b132b3eecc7a9c61a72235dbd19d9711 Mon Sep 17 00:00:00 2001 From: Nicki de Wet Date: Tue, 23 Apr 2024 10:27:24 +0200 Subject: [PATCH 035/222] Added Caveat regarding inheritance of Mute timings (#86678) Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> --- .../configure-notifications/create-notification-policy.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/sources/alerting/configure-notifications/create-notification-policy.md b/docs/sources/alerting/configure-notifications/create-notification-policy.md index bcd9d8a8064..3381cbd3137 100644 --- a/docs/sources/alerting/configure-notifications/create-notification-policy.md +++ b/docs/sources/alerting/configure-notifications/create-notification-policy.md @@ -97,6 +97,10 @@ An example of a valid matchers search input is: > All matched policies will be **exact** matches, we currently do not support regex-style or partial matching. +## Caveat + +Mute timings are not inherited from a parent notification policy, they have to be configured in full on each level. + ## Example An example of an alert configuration. From 08200bc533e14552e3236c9e9ba8d5359387652b Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Tue, 23 Apr 2024 11:04:53 +0100 Subject: [PATCH 036/222] Nav: Fix alerting links/special cases not selecting the right MegaMenu item (#85336) * Render current MegaMenu link as `aria-current=page` * Add overrides capability for mega menu links * Pass pageNav into getActiveItem so we can use override capability * Test MegaMenu special cases for starred & dashboards * Test that overrides for megamenu util works correctly * Alpha-sort megamenu overrides * Refactor util for getting active item for megamenu Update parameters to getActiveItem Update tests for getActiveItem * Fix test for starred dashboard and remove query param test Query param case happens differently in real app and is fiddly to test here * handle edge cases * restore handling home page test * fix dashboard settings * handle starring properly --------- Co-authored-by: Ashley Harrison --- .../AppChrome/MegaMenu/MegaMenu.test.tsx | 3 +- .../AppChrome/MegaMenu/MegaMenu.tsx | 2 +- .../AppChrome/MegaMenu/MegaMenuItemText.tsx | 1 + .../AppChrome/MegaMenu/utils.test.ts | 142 +++++------------- .../components/AppChrome/MegaMenu/utils.ts | 86 +++++------ public/app/core/reducers/navModel.ts | 4 + .../dashboard/components/DashNav/DashNav.tsx | 32 +++- .../DashboardSettings/DashboardSettings.tsx | 7 +- .../dashboard/containers/DashboardPage.tsx | 7 +- .../app/plugins/panel/dashlist/DashList.tsx | 24 ++- 10 files changed, 145 insertions(+), 163 deletions(-) diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.test.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.test.tsx index 43f9bc20180..68c4519423e 100644 --- a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.test.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.test.tsx @@ -2,14 +2,13 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { Router } from 'react-router-dom'; +import { TestProvider } from 'test/helpers/TestProvider'; import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; import { NavModelItem } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { locationService } from '@grafana/runtime'; -import { TestProvider } from '../../../../../test/helpers/TestProvider'; - import { MegaMenu } from './MegaMenu'; const setup = () => { diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx index 4f55f840990..7cb0e65dd83 100644 --- a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx @@ -32,7 +32,7 @@ export const MegaMenu = React.memo( .filter((item) => item.id !== 'profile' && item.id !== 'help') .map((item) => enrichWithInteractionTracking(item, state.megaMenuDocked)); - const activeItem = getActiveItem(navItems, location.pathname); + const activeItem = getActiveItem(navItems, state.sectionNav.node, location.pathname); const handleDockedMenu = () => { chrome.setMegaMenuDocked(!state.megaMenuDocked); diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenuItemText.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenuItemText.tsx index 6510fda9a27..f05cbd54ee9 100644 --- a/public/app/core/components/AppChrome/MegaMenu/MegaMenuItemText.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenuItemText.tsx @@ -38,6 +38,7 @@ export function MegaMenuItemText({ children, isActive, onClick, target, url }: P href={url} target={target} onClick={onClick} + {...(isActive && { 'aria-current': 'page' })} > {linkContent} diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts index eacea3cfdf9..e03c46022e7 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts @@ -1,7 +1,7 @@ -import { GrafanaConfig, locationUtil, NavModelItem } from '@grafana/data'; +import { NavModelItem } from '@grafana/data'; import { ContextSrv, setContextSrv } from 'app/core/services/context_srv'; -import { enrichHelpItem, getActiveItem, isMatchOrChildMatch } from './utils'; +import { enrichHelpItem, getActiveItem } from './utils'; jest.mock('../../../app_events', () => ({ publish: jest.fn(), @@ -44,143 +44,83 @@ describe('enrichConfigItems', () => { }); }); -describe('isMatchOrChildMatch', () => { - const mockChild: NavModelItem = { - text: 'Child', - url: '/dashboards/child', - }; - const mockItemToCheck: NavModelItem = { - text: 'Dashboards', - url: '/dashboards', - children: [mockChild], - }; - - it('returns true if the itemToCheck is an exact match with the searchItem', () => { - const searchItem = mockItemToCheck; - expect(isMatchOrChildMatch(mockItemToCheck, searchItem)).toBe(true); - }); - - it('returns true if the itemToCheck has a child that matches the searchItem', () => { - const searchItem = mockChild; - expect(isMatchOrChildMatch(mockItemToCheck, searchItem)).toBe(true); - }); - - it('returns false otherwise', () => { - const searchItem: NavModelItem = { - text: 'No match', - url: '/noMatch', - }; - expect(isMatchOrChildMatch(mockItemToCheck, searchItem)).toBe(false); - }); -}); - describe('getActiveItem', () => { + const starredDashboardUid = 'foo'; const mockNavTree: NavModelItem[] = [ { text: 'Item', url: '/item', - }, - { - text: 'Item with query param', - url: '/itemWithQueryParam?foo=bar', - }, - { - text: 'Item after subpath', - url: '/subUrl/itemAfterSubpath', + id: 'item', }, { text: 'Item with children', url: '/itemWithChildren', + id: 'item-with-children', children: [ { text: 'Child', url: '/child', + id: 'child', }, ], }, - { - text: 'Alerting item', - url: '/alerting/list', - }, { text: 'Base', url: '/', + id: 'home', }, { text: 'Starred', url: '/dashboards?starred', id: 'starred', + children: [ + { + id: `starred/${starredDashboardUid}`, + text: 'Lazy Loading', + url: `/d/${starredDashboardUid}/some-name`, + }, + ], }, { text: 'Dashboards', url: '/dashboards', - }, - { - text: 'More specific dashboard', - url: '/d/moreSpecificDashboard', + id: 'dashboards', }, ]; - beforeEach(() => { - locationUtil.initialize({ - config: { appSubUrl: '/subUrl' } as GrafanaConfig, - getVariablesUrlParams: () => ({}), - getTimeRangeForUrl: () => ({ from: 'now-7d', to: 'now' }), - }); - }); it('returns an exact match at the top level', () => { - const mockPathName = '/item'; - expect(getActiveItem(mockNavTree, mockPathName)).toEqual({ - text: 'Item', - url: '/item', - }); + const mockPage: NavModelItem = { + text: 'Some current page', + id: 'item', + }; + expect(getActiveItem(mockNavTree, mockPage)?.id).toEqual('item'); }); - it('returns an exact match ignoring root subpath', () => { - const mockPathName = '/itemAfterSubpath'; - expect(getActiveItem(mockNavTree, mockPathName)).toEqual({ - text: 'Item after subpath', - url: '/subUrl/itemAfterSubpath', - }); - }); - - it('returns an exact match ignoring query params', () => { - const mockPathName = '/itemWithQueryParam?bar=baz'; - expect(getActiveItem(mockNavTree, mockPathName)).toEqual({ - text: 'Item with query param', - url: '/itemWithQueryParam?foo=bar', - }); + it('returns parent item if no other matches in nav tree', () => { + const mockPage: NavModelItem = { + text: 'Some child page', + id: 'something-that-doesnt-exist', + parentItem: { + text: 'Some home page', + id: 'home', + }, + }; + expect(getActiveItem(mockNavTree, mockPage)?.id).toEqual('home'); }); it('returns an exact child match', () => { - const mockPathName = '/child'; - expect(getActiveItem(mockNavTree, mockPathName)).toEqual({ - text: 'Child', - url: '/child', - }); + const mockPage: NavModelItem = { + text: 'Some child page', + id: 'child', + }; + expect(getActiveItem(mockNavTree, mockPage)?.id).toEqual('child'); }); - it('returns the alerting link if the pathname is an alert notification', () => { - const mockPathName = '/alerting/notification/foo'; - expect(getActiveItem(mockNavTree, mockPathName)).toEqual({ - text: 'Alerting item', - url: '/alerting/list', - }); - }); - - it('returns the dashboards route link if the pathname starts with /d/', () => { - const mockPathName = '/d/foo'; - expect(getActiveItem(mockNavTree, mockPathName)).toEqual({ - text: 'Dashboards', - url: '/dashboards', - }); - }); - - it('returns a more specific link if one exists', () => { - const mockPathName = '/d/moreSpecificDashboard'; - expect(getActiveItem(mockNavTree, mockPathName)).toEqual({ - text: 'More specific dashboard', - url: '/d/moreSpecificDashboard', - }); + it('handles home page', () => { + const mockPage: NavModelItem = { + text: 'Something else', + id: 'not-home', + }; + expect(getActiveItem(mockNavTree, mockPage, '/')?.id).toEqual('home'); }); }); diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.ts b/public/app/core/components/AppChrome/MegaMenu/utils.ts index 9486c3ee5ce..90950cc25cd 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.ts @@ -1,6 +1,7 @@ -import { locationUtil, NavModelItem } from '@grafana/data'; +import { NavModelItem } from '@grafana/data'; import { config, reportInteraction } from '@grafana/runtime'; import { t } from 'app/core/internationalization'; +import { HOME_NAV_ID } from 'app/core/reducers/navModel'; import { ShowModalReactEvent } from '../../../../types/events'; import appEvents from '../../../app_events'; @@ -46,10 +47,6 @@ export const enrichWithInteractionTracking = (item: NavModelItem, megaMenuDocked return newItem; }; -export const isMatchOrChildMatch = (itemToCheck: NavModelItem, searchItem?: NavModelItem) => { - return Boolean(itemToCheck === searchItem || hasChildMatch(itemToCheck, searchItem)); -}; - export const hasChildMatch = (itemToCheck: NavModelItem, searchItem?: NavModelItem): boolean => { return Boolean( itemToCheck.children?.some((child) => { @@ -62,57 +59,48 @@ export const hasChildMatch = (itemToCheck: NavModelItem, searchItem?: NavModelIt ); }; -const stripQueryParams = (url?: string) => { - return url?.split('?')[0] ?? ''; -}; - -const isBetterMatch = (newMatch: NavModelItem, currentMatch?: NavModelItem) => { - const currentMatchUrl = stripQueryParams(currentMatch?.url); - const newMatchUrl = stripQueryParams(newMatch.url); - return newMatchUrl && newMatchUrl.length > currentMatchUrl?.length; -}; - export const getActiveItem = ( navTree: NavModelItem[], - pathname: string, - currentBestMatch?: NavModelItem + currentPage: NavModelItem, + url?: string ): NavModelItem | undefined => { - const dashboardLinkMatch = '/dashboards'; + const { id, parentItem } = currentPage; - for (const link of navTree) { - const linkWithoutParams = stripQueryParams(link.url); - const linkPathname = locationUtil.stripBaseFromUrl(linkWithoutParams); - if (linkPathname && link.id !== 'starred') { - if (linkPathname === pathname) { - // exact match - currentBestMatch = link; - break; - } else if (linkPathname !== '/' && pathname.startsWith(linkPathname)) { - // partial match - if (isBetterMatch(link, currentBestMatch)) { - currentBestMatch = link; - } - } else if (linkPathname === '/alerting/list' && pathname.startsWith('/alerting/notification/')) { - // alert channel match - // TODO refactor routes such that we don't need this custom logic - currentBestMatch = link; - break; - } else if (linkPathname === dashboardLinkMatch && pathname.startsWith('/d/')) { - // dashboard match - // TODO refactor routes such that we don't need this custom logic - if (isBetterMatch(link, currentBestMatch)) { - currentBestMatch = link; - } + // special case for the home page + if (url === '/') { + return navTree.find((item) => item.id === HOME_NAV_ID); + } + + // special case for profile as it's not part of the mega menu + if (currentPage.id === 'profile') { + return undefined; + } + + for (const navItem of navTree) { + const isIdMatch = Boolean(navItem.id && navItem.id === id); + const isTextUrlMatch = navItem.text === currentPage.text && navItem.url === currentPage.url; + + // ideally, we should only match on id + // unfortunately it's not a required property of the interface, and there are some cases + // where it's not set, particularly with child pages of plugins + // in those cases, we fall back to a text + url match + if (isIdMatch || isTextUrlMatch) { + return navItem; + } + + if (navItem.children) { + const childrenMatch = getActiveItem(navItem.children, currentPage); + if (childrenMatch) { + return childrenMatch; } } - if (link.children) { - currentBestMatch = getActiveItem(link.children, pathname, currentBestMatch); - } - if (stripQueryParams(currentBestMatch?.url) === pathname) { - return currentBestMatch; - } } - return currentBestMatch; + + if (parentItem) { + return getActiveItem(navTree, parentItem); + } + + return undefined; }; export function getEditionAndUpdateLinks(): NavModelItem[] { diff --git a/public/app/core/reducers/navModel.ts b/public/app/core/reducers/navModel.ts index d4968f54684..6717c66b350 100644 --- a/public/app/core/reducers/navModel.ts +++ b/public/app/core/reducers/navModel.ts @@ -73,6 +73,8 @@ export const updateNavIndex = createAction('navIndex/updateNavInde // Since the configuration subtitle includes the organization name, we include this action to update the org name if it changes. export const updateConfigurationSubtitle = createAction('navIndex/updateConfigurationSubtitle'); +export const removeNavIndex = createAction('navIndex/removeNavIndex'); + export const getItemWithNewSubTitle = (item: NavModelItem, subTitle: string): NavModelItem => ({ ...item, parentItem: { @@ -122,6 +124,8 @@ export const navIndexReducer = (state: NavIndex = initialState, action: AnyActio 'org-settings': getItemWithNewSubTitle(state['org-settings'], subTitle), apikeys: getItemWithNewSubTitle(state.apikeys, subTitle), }; + } else if (removeNavIndex.match(action)) { + delete state[action.payload]; } return state; diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 181677d5f2b..73375c196af 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -15,6 +15,7 @@ import { ConfirmModal, Badge, } from '@grafana/ui'; +import { updateNavIndex } from 'app/core/actions'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator'; import config from 'app/core/config'; @@ -22,7 +23,8 @@ import { useAppNotification } from 'app/core/copy/appNotification'; import { appEvents } from 'app/core/core'; import { useBusEvent } from 'app/core/hooks/useBusEvent'; import { t, Trans } from 'app/core/internationalization'; -import { setStarred } from 'app/core/reducers/navBarTree'; +import { ID_PREFIX, setStarred } from 'app/core/reducers/navBarTree'; +import { removeNavIndex } from 'app/core/reducers/navModel'; import AddPanelButton from 'app/features/dashboard/components/AddPanelButton/AddPanelButton'; import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; @@ -30,7 +32,7 @@ import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { updateTimeZoneForSession } from 'app/features/profile/state/reducers'; -import { KioskMode } from 'app/types'; +import { KioskMode, StoreState } from 'app/types'; import { DashboardMetaChangedEvent, ShowModalReactEvent } from 'app/types/events'; import { @@ -44,11 +46,17 @@ import { DashNavTimeControls } from './DashNavTimeControls'; import { ShareButton } from './ShareButton'; const mapDispatchToProps = { + removeNavIndex, setStarred, updateTimeZoneForSession, + updateNavIndex, }; -const connector = connect(null, mapDispatchToProps); +const mapStateToProps = (state: StoreState) => ({ + navIndex: state.navIndex, +}); + +const connector = connect(mapStateToProps, mapDispatchToProps); const selectors = e2eSelectors.pages.Dashboard.DashNav; @@ -121,10 +129,26 @@ export const DashNav = React.memo((props) => { const onStarDashboard = () => { DashboardInteractions.toolbarFavoritesClick(); const dashboardSrv = getDashboardSrv(); - const { dashboard, setStarred } = props; + const { dashboard, navIndex, removeNavIndex, setStarred, updateNavIndex } = props; dashboardSrv.starDashboard(dashboard.uid, Boolean(dashboard.meta.isStarred)).then((newState) => { setStarred({ id: dashboard.uid, title: dashboard.title, url: dashboard.meta.url ?? '', isStarred: newState }); + const starredNavItem = navIndex['starred']; + if (newState) { + starredNavItem.children?.push({ + id: ID_PREFIX + dashboard.uid, + text: dashboard.title, + url: dashboard.meta.url ?? '', + parentItem: starredNavItem, + }); + } else { + removeNavIndex(ID_PREFIX + dashboard.uid); + const indexToRemove = starredNavItem.children?.findIndex((element) => element.id === ID_PREFIX + dashboard.uid); + if (indexToRemove) { + starredNavItem.children?.splice(indexToRemove, 1); + } + } + updateNavIndex(starredNavItem); dashboard.meta.isStarred = newState; forceUpdate(); }); diff --git a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx index 23f24987ca3..766dc4a6613 100644 --- a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx @@ -53,7 +53,7 @@ export function DashboardSettings({ dashboard, editview, pageNav, sectionNav }: const canSave = dashboard.meta.canSave; const location = useLocation(); const editIndex = getEditIndex(location); - const subSectionNav = getSectionNav(pageNav, sectionNav, pages, currentPage, location); + const subSectionNav = getSectionNav(pageNav, sectionNav, pages, currentPage, location, dashboard.uid); const size = 'sm'; const actions = [ @@ -178,7 +178,8 @@ function getSectionNav( sectionNav: NavModel, pages: SettingsPage[], currentPage: SettingsPage, - location: H.Location + location: H.Location, + dashboardUid: string ): NavModel { const main: NavModelItem = { text: t('dashboard-settings.settings.title', 'Settings'), @@ -191,7 +192,7 @@ function getSectionNav( main.children = pages.map((page) => ({ text: page.title, icon: page.icon, - id: page.id, + id: `${dashboardUid}/${page.id}`, url: locationUtil.getUrlForPartial(location, { editview: page.id, editIndex: null }), active: page === currentPage, parentItem: main, diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 870be183763..2f1b1e16fb8 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -13,6 +13,7 @@ import { GrafanaContext, GrafanaContextType } from 'app/core/context/GrafanaCont import { createErrorNotification } from 'app/core/copy/appNotification'; import { getKioskMode } from 'app/core/navigation/kiosk'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { ID_PREFIX } from 'app/core/reducers/navBarTree'; import { getNavModel } from 'app/core/selectors/navModel'; import { PanelModel } from 'app/features/dashboard/state'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; @@ -477,7 +478,11 @@ function updateStatePageNavFromProps(props: Props, state: State): State { pageNav.parentItem = pageNav.parentItem; } } else { - sectionNav = getNavModel(props.navIndex, 'dashboards/browse'); + sectionNav = getNavModel( + props.navIndex, + ID_PREFIX + dashboard.uid, + getNavModel(props.navIndex, 'dashboards/browse') + ); } if (state.editPanel || state.viewPanel) { diff --git a/public/app/plugins/panel/dashlist/DashList.tsx b/public/app/plugins/panel/dashlist/DashList.tsx index e0a75e7e33b..876a3dd1b8c 100644 --- a/public/app/plugins/panel/dashlist/DashList.tsx +++ b/public/app/plugins/panel/dashlist/DashList.tsx @@ -11,16 +11,18 @@ import { urlUtil, } from '@grafana/data'; import { CustomScrollbar, useStyles2, IconButton } from '@grafana/ui'; +import { updateNavIndex } from 'app/core/actions'; import { getConfig } from 'app/core/config'; import { appEvents } from 'app/core/core'; import { useBusEvent } from 'app/core/hooks/useBusEvent'; -import { setStarred } from 'app/core/reducers/navBarTree'; +import { ID_PREFIX, setStarred } from 'app/core/reducers/navBarTree'; +import { removeNavIndex } from 'app/core/reducers/navModel'; import { getBackendSrv } from 'app/core/services/backend_srv'; import impressionSrv from 'app/core/services/impression_srv'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { DashboardSearchItem } from 'app/features/search/types'; import { VariablesChanged } from 'app/features/variables/types'; -import { useDispatch } from 'app/types'; +import { useDispatch, useSelector } from 'app/types'; import { Options } from './panelcfg.gen'; import { getStyles } from './styles'; @@ -102,6 +104,7 @@ async function fetchDashboards(options: Options, replaceVars: InterpolateFunctio export function DashList(props: PanelProps) { const [dashboards, setDashboards] = useState(new Map()); const dispatch = useDispatch(); + const navIndex = useSelector((state) => state.navIndex); useEffect(() => { fetchDashboards(props.options, props.replaceVariables).then((dashes) => { @@ -119,6 +122,23 @@ export function DashList(props: PanelProps) { updatedDashboards.set(dash?.uid ?? '', { ...dash, isStarred }); setDashboards(updatedDashboards); dispatch(setStarred({ id: uid ?? '', title, url, isStarred })); + + const starredNavItem = navIndex['starred']; + if (isStarred) { + starredNavItem.children?.push({ + id: ID_PREFIX + uid, + text: title, + url: url ?? '', + parentItem: starredNavItem, + }); + } else { + dispatch(removeNavIndex(ID_PREFIX + uid)); + const indexToRemove = starredNavItem.children?.findIndex((element) => element.id === ID_PREFIX + uid); + if (indexToRemove) { + starredNavItem.children?.splice(indexToRemove, 1); + } + } + dispatch(updateNavIndex(starredNavItem)); }; const [starredDashboards, recentDashboards, searchedDashboards] = useMemo(() => { From c849b6beaa7542cebef65022387df8e9d94f9f20 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:11:25 +0100 Subject: [PATCH 037/222] Update dependency react-i18next to v14 (#86753) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 1598365ea59..7af5c99baa9 100644 --- a/package.json +++ b/package.json @@ -370,7 +370,7 @@ "react-grid-layout": "1.4.4", "react-highlight-words": "0.20.0", "react-hook-form": "^7.49.2", - "react-i18next": "^12.0.0", + "react-i18next": "^14.0.0", "react-inlinesvg": "3.0.2", "react-loading-skeleton": "3.4.0", "react-moveable": "0.56.0", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 83be95aeafa..fcc36ee8ab0 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -90,7 +90,7 @@ "react-dropzone": "14.2.3", "react-highlight-words": "0.20.0", "react-hook-form": "^7.49.2", - "react-i18next": "^12.0.0", + "react-i18next": "^14.0.0", "react-inlinesvg": "3.0.2", "react-loading-skeleton": "3.4.0", "react-router-dom": "5.3.3", diff --git a/yarn.lock b/yarn.lock index 3661769da8c..3fe1af1eda5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1740,7 +1740,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.24.4, @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.14.0, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @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.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:7.24.4, @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.14.0, @babel/runtime@npm:^7.15.4, @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.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.24.4 resolution: "@babel/runtime@npm:7.24.4" dependencies: @@ -4377,7 +4377,7 @@ __metadata: react-dropzone: "npm:14.2.3" react-highlight-words: "npm:0.20.0" react-hook-form: "npm:^7.49.2" - react-i18next: "npm:^12.0.0" + react-i18next: "npm:^14.0.0" react-inlinesvg: "npm:3.0.2" react-loading-skeleton: "npm:3.4.0" react-router-dom: "npm:5.3.3" @@ -18834,7 +18834,7 @@ __metadata: react-grid-layout: "npm:1.4.4" react-highlight-words: "npm:0.20.0" react-hook-form: "npm:^7.49.2" - react-i18next: "npm:^12.0.0" + react-i18next: "npm:^14.0.0" react-inlinesvg: "npm:3.0.2" react-loading-skeleton: "npm:3.4.0" react-moveable: "npm:0.56.0" @@ -26513,21 +26513,21 @@ __metadata: languageName: node linkType: hard -"react-i18next@npm:^12.0.0": - version: 12.0.0 - resolution: "react-i18next@npm:12.0.0" +"react-i18next@npm:^14.0.0": + version: 14.1.1 + resolution: "react-i18next@npm:14.1.1" dependencies: - "@babel/runtime": "npm:^7.14.5" + "@babel/runtime": "npm:^7.23.9" html-parse-stringify: "npm:^3.0.1" peerDependencies: - i18next: ">= 19.0.0" + i18next: ">= 23.2.3" react: ">= 16.8.0" peerDependenciesMeta: react-dom: optional: true react-native: optional: true - checksum: 10/91da29572d0059783277b468999092d1d2a96efacdc29b0ab143e9b7ac22e293925c972b713ac08b57e65618b75f2848416e64751e210148f94bc87d86589e0f + checksum: 10/6ad103e0fb5eaedda8a87bf9f6ac0071ef73e7b1ec0d5a5d7ccd2c326183038b7d4722c87a253209f08878c51e5943be327cd48b0c6f3a87a8dc0fc2fca8cbfe languageName: node linkType: hard From 467d4231f1191c4a7d0ec8ba3b99b2ef31521ddc Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Tue, 23 Apr 2024 11:23:06 +0100 Subject: [PATCH 038/222] E2C: Remove ModalController from MigrationTokenPage (#86709) * Remove ModalController from MigrationTokenPage * Dont show modal or toast for error response * better --- .../features/migrate-to-cloud/api/index.ts | 7 +- .../MigrationTokenModal.tsx | 5 +- .../MigrationTokenPane/MigrationTokenPane.tsx | 79 ++++++++++--------- 3 files changed, 47 insertions(+), 44 deletions(-) diff --git a/public/app/features/migrate-to-cloud/api/index.ts b/public/app/features/migrate-to-cloud/api/index.ts index ac7a0b07d78..6ec39eaae3b 100644 --- a/public/app/features/migrate-to-cloud/api/index.ts +++ b/public/app/features/migrate-to-cloud/api/index.ts @@ -6,6 +6,9 @@ import { generatedAPI } from './endpoints.gen'; export const cloudMigrationAPI = generatedAPI.enhanceEndpoints({ addTagTypes: ['cloud-migration-config', 'cloud-migration-run', 'cloud-migration-run-list'], endpoints: { + // Cloud-side - create token + createCloudMigrationToken: suppressErrorsOnQuery, + // List Cloud Configs getMigrationList: { providesTags: ['cloud-migration-config'] /* should this be a -list? */, @@ -39,9 +42,7 @@ export const cloudMigrationAPI = generatedAPI.enhanceEndpoints({ invalidatesTags: ['cloud-migration-run-list'], }, - getDashboardByUid(endpoint) { - suppressErrorsOnQuery(endpoint); - }, + getDashboardByUid: suppressErrorsOnQuery, }, }); diff --git a/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenModal.tsx b/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenModal.tsx index d0b17c6a29d..c52cd7dcfcf 100644 --- a/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenModal.tsx +++ b/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenModal.tsx @@ -6,14 +6,15 @@ import { Trans, t } from 'app/core/internationalization'; import { TokenErrorAlert } from '../TokenErrorAlert'; interface Props { + isOpen: boolean; hideModal: () => void; migrationToken?: string; } -export const MigrationTokenModal = ({ hideModal, migrationToken }: Props) => { +export const MigrationTokenModal = ({ isOpen, hideModal, migrationToken }: Props) => { return ( diff --git a/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenPane.tsx b/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenPane.tsx index e580b40ec50..59b8123dced 100644 --- a/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenPane.tsx +++ b/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenPane.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useCallback, useState } from 'react'; -import { Box, Button, ModalsController, Text } from '@grafana/ui'; +import { Box, Button, Text } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; import { useCreateCloudMigrationTokenMutation } from '../../api'; @@ -11,52 +11,53 @@ import { MigrationTokenModal } from './MigrationTokenModal'; import { TokenStatus } from './TokenStatus'; export const MigrationTokenPane = () => { + const [showModal, setShowModal] = useState(false); const isFetchingStatus = false; // TODO: No API for this yet - const [createToken, createTokenResponse] = useCreateCloudMigrationTokenMutation(); + const [createTokenMutation, createTokenResponse] = useCreateCloudMigrationTokenMutation(); const hasToken = Boolean(createTokenResponse.data?.token); const isLoading = isFetchingStatus || createTokenResponse.isLoading; /* || deleteTokenResponse.isLoading */ + const handleGenerateToken = useCallback(async () => { + const resp = await createTokenMutation(); + if (!('error' in resp)) { + setShowModal(true); + } + }, [createTokenMutation]); + return ( - - {({ showModal, hideModal }) => ( - - - - Your self-managed Grafana instance will require a special authentication token to securely connect to this - cloud stack. + <> + + + + Your self-managed Grafana instance will require a special authentication token to securely connect to this + cloud stack. + + + + {createTokenResponse?.isError ? ( + + ) : ( + + + Current status: - + + )} - {createTokenResponse?.isError ? ( - - ) : ( - - - Current status: - - - )} + + - - - )} - + setShowModal(false)} + migrationToken={createTokenResponse.data?.token} + /> + ); }; From 37a86872db820f74ba522907f8bbfa267c767e60 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Mon, 22 Apr 2024 16:32:46 +0100 Subject: [PATCH 039/222] Remove port and add NODE_ENV to VSCode debug config --- .vscode/launch.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 99a3f849ca9..869d998655d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -53,7 +53,9 @@ "runtimeArgs": ["run", "jest", "--runInBand", "${file}"], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", - "port": 9229 + "env": { + "NODE_ENV": "test" + } }, { "name": "Debug Go test", From 97b66c2a95a50a50476433dfdd7fa3efef4f44ef Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Mon, 22 Apr 2024 16:33:05 +0100 Subject: [PATCH 040/222] Apply prettier fixes to VSCode debug config --- .vscode/launch.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 869d998655d..68815618ea6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,9 +19,7 @@ "program": "${workspaceFolder}/pkg/cmd/grafana/", "env": {}, "cwd": "${workspaceFolder}", - "args": ["apiserver", - "--secure-port=8443", - "--runtime-config=testdata.datasource.grafana.app/v0alpha1=true"] + "args": ["apiserver", "--secure-port=8443", "--runtime-config=testdata.datasource.grafana.app/v0alpha1=true"] }, { "name": "Run API Server (query-localhost)", @@ -31,12 +29,14 @@ "program": "${workspaceFolder}/pkg/cmd/grafana/", "env": {}, "cwd": "${workspaceFolder}", - "args": ["apiserver", - "--secure-port=8443", - "--runtime-config=query.grafana.app/v0alpha1=true", - "--grafana.authn.signing-keys-url=http://localhost:3000/api/signing-keys/keys", - "--hg-url=http://localhost:3000", - "--hg-key=$HGAPIKEY"] + "args": [ + "apiserver", + "--secure-port=8443", + "--runtime-config=query.grafana.app/v0alpha1=true", + "--grafana.authn.signing-keys-url=http://localhost:3000/api/signing-keys/keys", + "--hg-url=http://localhost:3000", + "--hg-key=$HGAPIKEY" + ] }, { "name": "Attach to Chrome", From 349df745784bc20d06ed7e0add8de0626b0188c4 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Mon, 22 Apr 2024 16:33:24 +0100 Subject: [PATCH 041/222] Rename debug target to "UI" rather than Jest --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 68815618ea6..81486b93841 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -46,7 +46,7 @@ "webRoot": "${workspaceFolder}" }, { - "name": "Debug Jest test", + "name": "Debug UI test", "type": "node", "request": "launch", "runtimeExecutable": "yarn", From c1f70219b5305853959922473114d65ee3ee3afc Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Tue, 23 Apr 2024 12:31:10 +0200 Subject: [PATCH 042/222] Chore: Bump express to 4.19.2 (#86487) chore(npm): bump express to 4.19.2 --- yarn.lock | 177 +++++++++++++++++++++++++++--------------------------- 1 file changed, 90 insertions(+), 87 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3fe1af1eda5..b2b0a13b297 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12506,21 +12506,23 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.19.2": - version: 1.19.2 - resolution: "body-parser@npm:1.19.2" +"body-parser@npm:1.20.2": + version: 1.20.2 + resolution: "body-parser@npm:1.20.2" dependencies: bytes: "npm:3.1.2" - content-type: "npm:~1.0.4" + content-type: "npm:~1.0.5" debug: "npm:2.6.9" - depd: "npm:~1.1.2" - http-errors: "npm:1.8.1" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + http-errors: "npm:2.0.0" iconv-lite: "npm:0.4.24" - on-finished: "npm:~2.3.0" - qs: "npm:6.9.7" - raw-body: "npm:2.4.3" + on-finished: "npm:2.4.1" + qs: "npm:6.11.0" + raw-body: "npm:2.5.2" type-is: "npm:~1.6.18" - checksum: 10/8a5f59d7e51b4000082a5e8bccf548cdbe77140520a55e380fecf5becf87a950b39e157a5c2a13334aa2ad45cc850b98e6c8652a4f033581c552001176dc9fc0 + unpipe: "npm:1.0.0" + checksum: 10/3cf171b82190cf91495c262b073e425fc0d9e25cc2bf4540d43f7e7bbca27d6a9eae65ca367b6ef3993eea261159d9d2ab37ce444e8979323952e12eb3df319a languageName: node linkType: hard @@ -13782,10 +13784,10 @@ __metadata: languageName: node linkType: hard -"content-type@npm:~1.0.4": - version: 1.0.4 - resolution: "content-type@npm:1.0.4" - checksum: 10/5ea85c5293475c0cdf2f84e2c71f0519ced565840fb8cbda35997cb67cc45b879d5b9dbd37760c4041ca7415a3687f8a5f2f87b556b2aaefa49c0f3436a346d4 +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10/585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 languageName: node linkType: hard @@ -13905,10 +13907,10 @@ __metadata: languageName: node linkType: hard -"cookie@npm:0.4.2": - version: 0.4.2 - resolution: "cookie@npm:0.4.2" - checksum: 10/2e1de9fdedca54881eab3c0477aeb067f281f3155d9cfee9d28dfb252210d09e85e9d175c0a60689661feb9e35e588515352f2456bc1f8e8db4267e05fd70137 +"cookie@npm:0.6.0": + version: 0.6.0 + resolution: "cookie@npm:0.6.0" + checksum: 10/c1f8f2ea7d443b9331680598b0ae4e6af18a618c37606d1bbdc75bec8361cce09fe93e727059a673f2ba24467131a9fb5a4eec76bb1b149c1b3e1ccb268dc583 languageName: node linkType: hard @@ -15340,6 +15342,13 @@ __metadata: languageName: node linkType: hard +"depd@npm:2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca + languageName: node + linkType: hard + "depd@npm:^1.1.2, depd@npm:~1.1.2": version: 1.1.2 resolution: "depd@npm:1.1.2" @@ -15361,10 +15370,10 @@ __metadata: languageName: node linkType: hard -"destroy@npm:~1.0.4": - version: 1.0.4 - resolution: "destroy@npm:1.0.4" - checksum: 10/da9ab4961dc61677c709da0c25ef01733042614453924d65636a7db37308fef8a24cd1e07172e61173d471ca175371295fbc984b0af5b2b4ff47cd57bd784c03 +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: 10/0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 languageName: node linkType: hard @@ -17175,40 +17184,41 @@ __metadata: linkType: hard "express@npm:^4.17.3": - version: 4.17.3 - resolution: "express@npm:4.17.3" + version: 4.19.2 + resolution: "express@npm:4.19.2" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" - body-parser: "npm:1.19.2" + body-parser: "npm:1.20.2" content-disposition: "npm:0.5.4" content-type: "npm:~1.0.4" - cookie: "npm:0.4.2" + cookie: "npm:0.6.0" cookie-signature: "npm:1.0.6" debug: "npm:2.6.9" - depd: "npm:~1.1.2" + depd: "npm:2.0.0" encodeurl: "npm:~1.0.2" escape-html: "npm:~1.0.3" etag: "npm:~1.8.1" - finalhandler: "npm:~1.1.2" + finalhandler: "npm:1.2.0" fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" merge-descriptors: "npm:1.0.1" methods: "npm:~1.1.2" - on-finished: "npm:~2.3.0" + on-finished: "npm:2.4.1" parseurl: "npm:~1.3.3" path-to-regexp: "npm:0.1.7" proxy-addr: "npm:~2.0.7" - qs: "npm:6.9.7" + qs: "npm:6.11.0" range-parser: "npm:~1.2.1" safe-buffer: "npm:5.2.1" - send: "npm:0.17.2" - serve-static: "npm:1.14.2" + send: "npm:0.18.0" + serve-static: "npm:1.15.0" setprototypeof: "npm:1.2.0" - statuses: "npm:~1.5.0" + statuses: "npm:2.0.1" type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10/e3970a6cccdeec918d249c9dd8b0a83bd64eb5730f33c69f449763d16155c3ff9631a3f5aa6191c7d56a6bb9d8b40fcce69c12563bf593208ec2b4902d7b8475 + checksum: 10/3fcd792536f802c059789ef48db3851b87e78fba103423e524144d79af37da7952a2b8d4e1a007f423329c7377d686d9476ac42e7d9ea413b80345d495e30a3a languageName: node linkType: hard @@ -17523,18 +17533,18 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:~1.1.2": - version: 1.1.2 - resolution: "finalhandler@npm:1.1.2" +"finalhandler@npm:1.2.0": + version: 1.2.0 + resolution: "finalhandler@npm:1.2.0" dependencies: debug: "npm:2.6.9" encodeurl: "npm:~1.0.2" escape-html: "npm:~1.0.3" - on-finished: "npm:~2.3.0" + on-finished: "npm:2.4.1" parseurl: "npm:~1.3.3" - statuses: "npm:~1.5.0" + statuses: "npm:2.0.1" unpipe: "npm:~1.0.0" - checksum: 10/351e99a889abf149eb3edb24568586469feeb3019f5eafb9b31e632a5ad886f12a5595a221508245e6a37da69ae866c9fb411eb541a844238e2c900f63ac1576 + checksum: 10/635718cb203c6d18e6b48dfbb6c54ccb08ea470e4f474ddcef38c47edcf3227feec316f886dd701235997d8af35240cae49856721ce18f539ad038665ebbf163 languageName: node linkType: hard @@ -19421,16 +19431,16 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:1.8.1": - version: 1.8.1 - resolution: "http-errors@npm:1.8.1" +"http-errors@npm:2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" dependencies: - depd: "npm:~1.1.2" + depd: "npm:2.0.0" inherits: "npm:2.0.4" setprototypeof: "npm:1.2.0" - statuses: "npm:>= 1.5.0 < 2" + statuses: "npm:2.0.1" toidentifier: "npm:1.0.1" - checksum: 10/76fc491bd8df2251e21978e080d5dae20d9736cfb29bb72b5b76ec1bcebb1c14f0f58a3a128dd89288934379d2173cfb0421c571d54103e93dd65ef6243d64d8 + checksum: 10/0e7f76ee8ff8a33e58a3281a469815b893c41357378f408be8f6d4aa7d1efafb0da064625518e7078381b6a92325949b119dc38fcb30bdbc4e3a35f78c44c439 languageName: node linkType: hard @@ -24219,7 +24229,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:^2.4.1": +"on-finished@npm:2.4.1, on-finished@npm:^2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -24228,15 +24238,6 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:~2.3.0": - version: 2.3.0 - resolution: "on-finished@npm:2.3.0" - dependencies: - ee-first: "npm:1.1.1" - checksum: 10/1db595bd963b0124d6fa261d18320422407b8f01dc65863840f3ddaaf7bcad5b28ff6847286703ca53f4ec19595bd67a2f1253db79fc4094911ec6aa8df1671b - languageName: node - linkType: hard - "on-headers@npm:~1.0.2": version: 1.0.2 resolution: "on-headers@npm:1.0.2" @@ -25852,10 +25853,12 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.9.7": - version: 6.9.7 - resolution: "qs@npm:6.9.7" - checksum: 10/fb364b54bf4f092a095554968f5abf06036cfe359c9aba258a81b0c0366f625a46098fe1224b2a71ee2f88642470af391c7a8a1496508eca29c37093293f91a9 +"qs@npm:6.11.0": + version: 6.11.0 + resolution: "qs@npm:6.11.0" + dependencies: + side-channel: "npm:^1.0.4" + checksum: 10/5a3bfea3e2f359ede1bfa5d2f0dbe54001aa55e40e27dc3e60fab814362d83a9b30758db057c2011b6f53a2d4e4e5150194b5bac45372652aecb3e3c0d4b256e languageName: node linkType: hard @@ -25976,15 +25979,15 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:2.4.3": - version: 2.4.3 - resolution: "raw-body@npm:2.4.3" +"raw-body@npm:2.5.2": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" dependencies: bytes: "npm:3.1.2" - http-errors: "npm:1.8.1" + http-errors: "npm:2.0.0" iconv-lite: "npm:0.4.24" unpipe: "npm:1.0.0" - checksum: 10/b3a7cfacfa00778abce59fe1c698bd44edfdbea9ddd39e22c553dcd50a0376f4c3ad0e650f1ba9d20495bab81251844e14448292071487bd372e506faf0a1a2e + checksum: 10/863b5171e140546a4d99f349b720abac4410338e23df5e409cfcc3752538c9caf947ce382c89129ba976f71894bd38b5806c774edac35ebf168d02aa1ac11a95 languageName: node linkType: hard @@ -28324,24 +28327,24 @@ __metadata: languageName: node linkType: hard -"send@npm:0.17.2": - version: 0.17.2 - resolution: "send@npm:0.17.2" +"send@npm:0.18.0": + version: 0.18.0 + resolution: "send@npm:0.18.0" dependencies: debug: "npm:2.6.9" - depd: "npm:~1.1.2" - destroy: "npm:~1.0.4" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" encodeurl: "npm:~1.0.2" escape-html: "npm:~1.0.3" etag: "npm:~1.8.1" fresh: "npm:0.5.2" - http-errors: "npm:1.8.1" + http-errors: "npm:2.0.0" mime: "npm:1.6.0" ms: "npm:2.1.3" - on-finished: "npm:~2.3.0" + on-finished: "npm:2.4.1" range-parser: "npm:~1.2.1" - statuses: "npm:~1.5.0" - checksum: 10/b1e4f9b99a5571626dad1d4401363f6107e245c84e91ccae221ab55fea4d1f12be1b9ef657235381e2b9d3a5840b8ceb4d20c9c322e85535b587b12676bc340c + statuses: "npm:2.0.1" + checksum: 10/ec66c0ad109680ad8141d507677cfd8b4e40b9559de23191871803ed241718e99026faa46c398dcfb9250676076573bd6bfe5d0ec347f88f4b7b8533d1d391cb languageName: node linkType: hard @@ -28391,15 +28394,15 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.14.2": - version: 1.14.2 - resolution: "serve-static@npm:1.14.2" +"serve-static@npm:1.15.0": + version: 1.15.0 + resolution: "serve-static@npm:1.15.0" dependencies: encodeurl: "npm:~1.0.2" escape-html: "npm:~1.0.3" parseurl: "npm:~1.3.3" - send: "npm:0.17.2" - checksum: 10/b84f9d58b07db2e4d6b1f60e60a27839f5247331b9bbfa1a90eda3523d8bc58585c914e069e785bb2645668e0b9cdd1a305bc1aa81c4284f5ead103741bb705c + send: "npm:0.18.0" + checksum: 10/699b2d4c29807a51d9b5e0f24955346911437aebb0178b3c4833ad30d3eca93385ff9927254f5c16da345903cad39d9cd4a532198c95a5129cc4ed43911b15a4 languageName: node linkType: hard @@ -29255,20 +29258,20 @@ __metadata: languageName: node linkType: hard -"statuses@npm:>= 1.4.0 < 2, statuses@npm:>= 1.5.0 < 2, statuses@npm:~1.5.0": - version: 1.5.0 - resolution: "statuses@npm:1.5.0" - checksum: 10/c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c - languageName: node - linkType: hard - -"statuses@npm:^2.0.1": +"statuses@npm:2.0.1, statuses@npm:^2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" checksum: 10/18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb languageName: node linkType: hard +"statuses@npm:>= 1.4.0 < 2": + version: 1.5.0 + resolution: "statuses@npm:1.5.0" + checksum: 10/c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c + languageName: node + linkType: hard + "std-env@npm:^3.7.0": version: 3.7.0 resolution: "std-env@npm:3.7.0" From d10303fb05f94d1eaf579e134be08acaa8a72083 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Tue, 23 Apr 2024 12:31:27 +0200 Subject: [PATCH 043/222] Chore: Bump ip to 2.0.1 (#86486) chore(npm): bump ip to 2.0.1 --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index b2b0a13b297..fa24feb2ff1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19962,9 +19962,9 @@ __metadata: linkType: hard "ip@npm:^2.0.0": - version: 2.0.0 - resolution: "ip@npm:2.0.0" - checksum: 10/1270b11e534a466fb4cf4426cbcc3a907c429389f7f4e4e3b288b42823562e88d6a509ceda8141a507de147ca506141f745005c0aa144569d94cf24a54eb52bc + version: 2.0.1 + resolution: "ip@npm:2.0.1" + checksum: 10/d6dd154e1bc5e8725adfdd6fb92218635b9cbe6d873d051bd63b178f009777f751a5eea4c67021723a7056325fc3052f8b6599af0a2d56f042c93e684b4a0349 languageName: node linkType: hard From e6799be13c8f1c9e765b0e300f742504cddb46d0 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Tue, 23 Apr 2024 12:32:17 +0200 Subject: [PATCH 044/222] Chore: Bump webpack-dev-middleware to 6.1.3 (#86482) chore(npm): bump version of webpack-dev-middleware to 6.1.3 --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index fa24feb2ff1..dfb3a877044 100644 --- a/yarn.lock +++ b/yarn.lock @@ -31573,8 +31573,8 @@ __metadata: linkType: hard "webpack-dev-middleware@npm:^6.1.1": - version: 6.1.1 - resolution: "webpack-dev-middleware@npm:6.1.1" + version: 6.1.3 + resolution: "webpack-dev-middleware@npm:6.1.3" dependencies: colorette: "npm:^2.0.10" memfs: "npm:^3.4.12" @@ -31586,7 +31586,7 @@ __metadata: peerDependenciesMeta: webpack: optional: true - checksum: 10/b0637584f18b02174fd7fc2e6278efb8e2fb5308abe4ffe73658e59ff53a62c05686f161b06bd5c41d42611aa395b8c8f087d7ff8cf2304232c097a694a5b94e + checksum: 10/ee699430c33c4dfa2a016becc85e32a9b04aa0b6edbce0bb173c4dfd29c80c77d192d14fd2f2ec500dbdede4e0f1c5557993aa20a04a44190750a1e8e13f6d67 languageName: node linkType: hard From 835b968b086a99fd6615cdbc78f848563945e693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 23 Apr 2024 12:35:16 +0200 Subject: [PATCH 045/222] DashboardScene: Fixes deleting dirty dashboard (#86479) * DashboardScene: Fixes deleting dirty dashboard * refactor + unit test --- .../scene/DashboardScene.test.tsx | 19 ++++ .../dashboard-scene/scene/DashboardScene.tsx | 8 ++ .../settings/DeleteDashboardButton.tsx | 90 +++++++++++++++++++ .../settings/GeneralSettingsEditView.tsx | 13 +-- 4 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx index 0bbe46104db..041c692036f 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx @@ -1,4 +1,5 @@ import { CoreApp, LoadingState, getDefaultTimeRange } from '@grafana/data'; +import { locationService } from '@grafana/runtime'; import { sceneGraph, SceneGridLayout, @@ -71,6 +72,12 @@ jest.mock('app/features/playlist/PlaylistSrv', () => ({ stop: jest.fn(), }, })); + +jest.mock('app/features/manage-dashboards/state/actions', () => ({ + ...jest.requireActual('app/features/manage-dashboards/state/actions'), + deleteDashboard: jest.fn().mockResolvedValue({}), +})); + const worker = createWorker(); mockResultsOfDetectChangesWorker({ hasChanges: true, hasTimeChanges: false, hasVariableValueChanges: false }); @@ -884,6 +891,18 @@ describe('DashboardScene', () => { }); }); + describe('Deleting dashboard', () => { + it('Should mark it non dirty before navigating to root', async () => { + const scene = buildTestScene(); + scene.setState({ isDirty: true }); + + locationService.push('/d/adsdas'); + await scene.deleteDashboard(); + + expect(scene.state.isDirty).toBe(false); + }); + }); + describe('Enriching data requests', () => { let scene: DashboardScene; diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index eef7d822012..eebc3be983e 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -24,6 +24,7 @@ import store from 'app/core/store'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; +import { deleteDashboard } from 'app/features/manage-dashboards/state/actions'; import { VariablesChanged } from 'app/features/variables/types'; import { DashboardDTO, DashboardMeta, SaveDashboardResponseDTO } from 'app/types'; import { ShowConfirmModalEvent } from 'app/types/events'; @@ -832,6 +833,13 @@ export class DashboardScene extends SceneObjectBase { public setInitialSaveModel(saveModel: Dashboard) { this._initialSaveModel = saveModel; } + + public async deleteDashboard() { + await deleteDashboard(this.state.uid!, true); + // Need to mark it non dirty to navigate away without unsaved changes warning + this.setState({ isDirty: false }); + locationService.replace('/'); + } } export class DashboardVariableDependency implements SceneVariableDependencyConfigLike { diff --git a/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx b/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx new file mode 100644 index 00000000000..a896816dc7a --- /dev/null +++ b/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { useAsyncFn, useToggle } from 'react-use'; + +import { Button, ConfirmModal, Modal } from '@grafana/ui'; +import { Trans } from 'app/core/internationalization'; + +import { DashboardScene } from '../scene/DashboardScene'; + +interface ButtonProps { + dashboard: DashboardScene; +} + +export function DeleteDashboardButton({ dashboard }: ButtonProps) { + const [showModal, toggleModal] = useToggle(false); + + return ( + <> + + + {showModal && } + + ); +} + +interface ModalProps { + dashboard: DashboardScene; + onClose: () => void; +} + +function DeleteDashboardModal({ dashboard, onClose }: ModalProps) { + const [, onConfirm] = useAsyncFn(async () => { + onClose(); + await dashboard.deleteDashboard(); + }, [dashboard, onClose]); + + if (dashboard.state.meta.provisioned) { + return ; + } + + return ( + +

Do you want to delete this dashboard?

+

{dashboard.state.title}

+ + } + onConfirm={onConfirm} + onDismiss={onClose} + title="Delete" + icon="trash-alt" + confirmText="Delete" + /> + ); +} + +function ProvisionedDeleteModal({ dashboard, onClose }: ModalProps) { + return ( + +

+ This dashboard is managed by Grafana provisioning and cannot be deleted. Remove the dashboard from the config + file to delete it. +

+

+ + See{' '} + + documentation + {' '} + for more information about provisioning. + +
+ File path: {dashboard.state.meta.provisionedExternalId} +

+ + + +
+ ); +} diff --git a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx index 669dcc109d8..ec68a8db964 100644 --- a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx @@ -19,7 +19,6 @@ import { Page } from 'app/core/components/Page/Page'; import { FolderPicker } from 'app/core/components/Select/FolderPicker'; import { t, Trans } from 'app/core/internationalization'; import { TimePickerSettings } from 'app/features/dashboard/components/DashboardSettings/TimePickerSettings'; -import { DeleteDashboardButton } from 'app/features/dashboard/components/DeleteDashboard/DeleteDashboardButton'; import { GenAIDashDescriptionButton } from 'app/features/dashboard/components/GenAI/GenAIDashDescriptionButton'; import { GenAIDashTitleButton } from 'app/features/dashboard/components/GenAI/GenAIDashTitleButton'; @@ -29,6 +28,7 @@ import { NavToolbarActions } from '../scene/NavToolbarActions'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { getDashboardSceneFor } from '../utils/utils'; +import { DeleteDashboardButton } from './DeleteDashboardButton'; import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils'; export interface GeneralSettingsEditViewState extends DashboardEditViewState {} @@ -161,9 +161,12 @@ export class GeneralSettingsEditView this.getCursorSync()?.setState({ sync: value }); }; + public onDeleteDashboard = () => {}; + static Component = ({ model }: SceneComponentProps) => { - const { navModel, pageNav } = useDashboardEditPageNav(model.getDashboard(), model.getUrlKey()); - const { title, description, tags, meta, editable } = model.getDashboard().useState(); + const dashboard = model.getDashboard(); + const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey()); + const { title, description, tags, meta, editable } = dashboard.useState(); const { sync: graphTooltip } = model.getCursorSync()?.useState() || {}; const { timeZone, weekStart, UNSAFE_nowDelay: nowDelay } = model.getTimeRange().useState(); const { intervals } = model.getRefreshPicker().useState(); @@ -172,7 +175,7 @@ export class GeneralSettingsEditView return ( - +
- {meta.canDelete && } + {meta.canDelete && }
); From 3d55602fde5d4f7cca8850178e0e1f1d06271afe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 12:52:16 +0100 Subject: [PATCH 046/222] Update `make docs` procedure (#86746) Co-authored-by: grafanabot --- docs/make-docs | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/docs/make-docs b/docs/make-docs index 43efdb5faad..fc4f3d7a148 100755 --- a/docs/make-docs +++ b/docs/make-docs @@ -6,6 +6,22 @@ # [Semantic versioning](https://semver.org/) is used to help the reader identify the significance of changes. # Changes are relevant to this script and the support docs.mk GNU Make interface. # +# ## 6.1.0 (2024-04-22) +# +# ### Changed +# +# - Mount volumes with SELinux labels. +# +# https://docs.docker.com/storage/bind-mounts/#configure-the-selinux-label +# +# ## 6.1.0 (2024-04-22) +# +# ### Added +# +# - Pseudo project for including only website resources and no website content. +# +# Facilitates testing shortcodes and layout changes with a small documentation set instead of Grafana Cloud or the entire website. +# # ## 6.0.1 (2024-02-28) # # ### Added @@ -300,6 +316,7 @@ SOURCES_helm_charts_tempo_distributed='tempo' SOURCES_opentelemetry='opentelemetry-docs' SOURCES_plugins_grafana_datadog_datasource='datadog-datasource' SOURCES_plugins_grafana_oracle_datasource='oracle-datasource' +SOURCES_resources='website' VERSIONS_as_code='UNVERSIONED' VERSIONS_grafana_cloud='UNVERSIONED' @@ -311,6 +328,7 @@ VERSIONS_grafana_cloud_frontend_observability_faro_web_sdk='UNVERSIONED' VERSIONS_opentelemetry='UNVERSIONED' VERSIONS_plugins_grafana_datadog_datasource='latest' VERSIONS_plugins_grafana_oracle_datasource='latest' +VERSIONS_resources='UNVERSIONED' VERSIONS_technical_documentation='UNVERSIONED' VERSIONS_website='UNVERSIONED' VERSIONS_writers_toolkit='UNVERSIONED' @@ -321,6 +339,7 @@ PATHS_helm_charts_tempo_distributed='docs/sources/helm-charts/tempo-distributed' PATHS_mimir='docs/sources/mimir' PATHS_plugins_grafana_datadog_datasource='docs/sources' PATHS_plugins_grafana_oracle_datasource='docs/sources' +PATHS_resources='content' PATHS_tempo='docs/sources/tempo' PATHS_website='content' @@ -584,6 +603,11 @@ POSIX_HERESTRING proj_to_url_src_dst_ver "$(new_proj helm-charts/mimir-distributed "${_version}")" proj_to_url_src_dst_ver "$(new_proj enterprise-metrics "${_version}")" ;; + resources) + _repo="$(repo_path website)" + echo "arbitrary^${_repo}/config^/hugo/config" "arbitrary^${_repo}/layouts^/hugo/layouts" "arbitrary^${_repo}/scripts^/hugo/scripts" + unset _repo + ;; traces) proj_to_url_src_dst_ver "$(new_proj tempo "${_version}")" proj_to_url_src_dst_ver "$(new_proj enterprise-traces "${_version}")" @@ -617,7 +641,7 @@ $x POSIX_HERESTRING if [ -n "${url}" ]; then - if [ "${_url}" != "arbitrary" ]; then + if [ "${url}" != arbitrary ]; then printf '\r %s\r\n' "${url}" fi fi @@ -670,9 +694,9 @@ POSIX_HERESTRING fi _repo="$(repo_path website)" - volumes="--volume=${_repo}/config:/hugo/config" - volumes="${volumes} --volume=${_repo}/layouts:/hugo/layouts" - volumes="${volumes} --volume=${_repo}/scripts:/hugo/scripts" + volumes="--volume=${_repo}/config:/hugo/config:z" + volumes="${volumes} --volume=${_repo}/layouts:/hugo/layouts:z" + volumes="${volumes} --volume=${_repo}/scripts:/hugo/scripts:z" fi unset _project _repo done @@ -682,7 +706,7 @@ for x in ${url_src_dst_vers}; do $x POSIX_HERESTRING - if [ "${_url}" != "arbitrary" ]; then + if [ "${_url}" != arbitrary ]; then if [ ! -f "${_src}/_index.md" ]; then errr "Index file '${_src}/_index.md' does not exist." note "Is '${_src}' the correct source directory?" @@ -693,9 +717,9 @@ POSIX_HERESTRING debg "Mounting '${_src}' at container path '${_dst}'" if [ -z "${volumes}" ]; then - volumes="--volume=${_src}:${_dst}" + volumes="--volume=${_src}:${_dst}:z" else - volumes="${volumes} --volume=${_src}:${_dst}" + volumes="${volumes} --volume=${_src}:${_dst}:z" fi if [ -n "${_ver}" ] && [ "${_ver}" != 'UNVERSIONED' ]; then @@ -789,7 +813,7 @@ fi ${WEBSITE_EXEC} EOF chmod +x "${tempfile}" - volumes="${volumes} --volume=${tempfile}:/entrypoint" + volumes="${volumes} --volume=${tempfile}:/entrypoint:z" readonly volumes IFS='' read -r cmd < Date: Tue, 23 Apr 2024 14:08:34 +0200 Subject: [PATCH 047/222] FE Sandbox: Fix worker post message not handling proxy objects correctly (#86654) * FE Sandbox: Fix worker post message not handling proxy objects correctly * use expect error instead of ignore * use assertion instead of ignore * Fix formatting --- .../plugins/sandbox/document_sandbox.ts | 23 ++++++++++++++++++- public/app/features/plugins/sandbox/utils.ts | 22 ++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/public/app/features/plugins/sandbox/document_sandbox.ts b/public/app/features/plugins/sandbox/document_sandbox.ts index 9dcb1a3a1aa..fdd4b0c9740 100644 --- a/public/app/features/plugins/sandbox/document_sandbox.ts +++ b/public/app/features/plugins/sandbox/document_sandbox.ts @@ -6,7 +6,7 @@ import { CustomVariableSupport, DataSourceApi } from '@grafana/data'; import { config } from '@grafana/runtime'; import { forbiddenElements } from './constants'; -import { isReactClassComponent, logWarning } from './utils'; +import { isReactClassComponent, logWarning, unboxNearMembraneProxies } from './utils'; // IMPORTANT: NEVER export this symbol from a public (e.g `@grafana/*`) package const SANDBOX_LIVE_VALUE = Symbol.for('@@SANDBOX_LIVE_VALUE'); @@ -194,9 +194,30 @@ export function patchWebAPIs() { if (!nativeAPIsPatched) { nativeAPIsPatched = true; patchHistoryReplaceState(); + patchWorkerPostMessage(); } } +/* + * + * Worker.postMessage uses internally structureClone which won't work with proxies. + * + * In case where the blue realm code is directly handling proxy objects that + * should be send over a post message the blue realm will call postMessage and try to + * send the proxy resulting in an error. + * + * This makes sure all proxies are unboxed before being sent over the post message + */ +function patchWorkerPostMessage() { + const originalPostMessage = Worker.prototype.postMessage; + Object.defineProperty(Worker.prototype, 'postMessage', { + value: function (...args: Parameters) { + // eslint-disable-next-line + return originalPostMessage.apply(this, unboxNearMembraneProxies(args) as typeof args); + }, + }); +} + /* * window.history.replaceState is a native API that won't work with proxies * so we need to patch it to unwrap any possible proxies you pass to it. diff --git a/public/app/features/plugins/sandbox/utils.ts b/public/app/features/plugins/sandbox/utils.ts index 30aab341881..af97b19c9ec 100644 --- a/public/app/features/plugins/sandbox/utils.ts +++ b/public/app/features/plugins/sandbox/utils.ts @@ -1,4 +1,5 @@ import { isNearMembraneProxy } from '@locker/near-membrane-shared'; +import { cloneDeep } from 'lodash'; import React from 'react'; import { PluginSignatureType, PluginType } from '@grafana/data'; @@ -112,3 +113,24 @@ export function unboxRegexesFromMembraneProxy(structure: unknown): unknown { } return structure; } + +export function unboxNearMembraneProxies(structure: unknown): unknown { + if (!structure) { + return structure; + } + + if (isNearMembraneProxy(structure)) { + return cloneDeep(structure); + } + + if (Array.isArray(structure)) { + return structure.map(unboxNearMembraneProxies); + } + if (typeof structure === 'object') { + return Object.keys(structure).reduce((acc, key) => { + Reflect.set(acc, key, unboxNearMembraneProxies(Reflect.get(structure, key))); + return acc; + }, {}); + } + return structure; +} From 8b7c2a459bd4a1b00709c742755636cd1b4ab7ea Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 23 Apr 2024 14:36:40 +0200 Subject: [PATCH 048/222] Alerting: Implement SaveAndApplyDefaultConfig in the forked Alertmanager (remote primary mode) (#85668) * Alerting: Implement SaveAndApplyDefaultConfig in the forked Alertmanager (remote primary) * log the error for the internal AM instead of returning it --- .../remote/forked_alertmanager_test.go | 53 ++++++++++++------- .../remote_primary_forked_alertmanager.go | 9 +++- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/pkg/services/ngalert/remote/forked_alertmanager_test.go b/pkg/services/ngalert/remote/forked_alertmanager_test.go index ca829cb068e..278d38efaa0 100644 --- a/pkg/services/ngalert/remote/forked_alertmanager_test.go +++ b/pkg/services/ngalert/remote/forked_alertmanager_test.go @@ -107,7 +107,7 @@ func TestForkedAlertmanager_ModeRemoteSecondary(t *testing.T) { }) t.Run("SaveAndApplyDefaultConfig", func(tt *testing.T) { - // SaveAndApplyDefaultConfig should only be called on the remote Alertmanager. + // SaveAndApplyDefaultConfig should only be called on the internal Alertmanager. // State and configuration are updated on an interval. internal, _, forked := genTestAlertmanagers(tt, modeRemoteSecondary) internal.EXPECT().SaveAndApplyDefaultConfig(ctx).Return(nil).Once() @@ -368,25 +368,42 @@ func TestForkedAlertmanager_ModeRemotePrimary(t *testing.T) { expErr := errors.New("test error") t.Run("ApplyConfig", func(tt *testing.T) { - { - // If the remote Alertmanager is not ready, ApplyConfig should be called on both Alertmanagers, - // first on the remote, then on the internal. - internal, remote, forked := genTestAlertmanagers(tt, modeRemotePrimary) - remoteCall := remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once() - internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once().NotBefore(remoteCall) - require.NoError(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{})) + // If the remote Alertmanager is not ready, ApplyConfig should be called on both Alertmanagers, + // first on the remote, then on the internal. + internal, remote, forked := genTestAlertmanagers(tt, modeRemotePrimary) + remoteCall := remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once() + internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once().NotBefore(remoteCall) + require.NoError(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{})) - // An error in the remote Alertmanager should be returned. - _, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) - remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(expErr).Once() - require.ErrorIs(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{}), expErr) + // An error in the remote Alertmanager should be returned. + _, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(expErr).Once() + require.ErrorIs(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{}), expErr) - // An error in the internal Alertmanager should not be returned. - internal, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) - remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once() - internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(expErr).Once() - require.NoError(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{})) - } + // An error in the internal Alertmanager should not be returned. + internal, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().ApplyConfig(ctx, mock.Anything).Return(nil).Once() + internal.EXPECT().ApplyConfig(ctx, mock.Anything).Return(expErr).Once() + require.NoError(tt, forked.ApplyConfig(ctx, &models.AlertConfiguration{})) + }) + + t.Run("SaveAndApplyDefaultConfig", func(tt *testing.T) { + // SaveAndApplyDefaultConfig should be called on both Alertmanagers. + internal, remote, forked := genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().SaveAndApplyDefaultConfig(ctx).Return(nil).Once() + internal.EXPECT().SaveAndApplyDefaultConfig(ctx).Return(nil).Once() + require.NoError(tt, forked.SaveAndApplyDefaultConfig(ctx)) + + // An error in the remote Alertmanager should be returned. + _, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().SaveAndApplyDefaultConfig(ctx).Return(expErr).Once() + require.ErrorIs(tt, forked.SaveAndApplyDefaultConfig(ctx), expErr) + + // An error in the internal Alertmanager should not be returned. + internal, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().SaveAndApplyDefaultConfig(ctx).Return(nil).Once() + internal.EXPECT().SaveAndApplyDefaultConfig(ctx).Return(expErr).Once() + require.NoError(tt, forked.SaveAndApplyDefaultConfig(ctx)) }) t.Run("GetStatus", func(tt *testing.T) { diff --git a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go index 37b955db493..1a360e4f513 100644 --- a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go @@ -39,13 +39,18 @@ func (fam *RemotePrimaryForkedAlertmanager) ApplyConfig(ctx context.Context, con return nil } -// TODO: save the new configuration hash in memory. func (fam *RemotePrimaryForkedAlertmanager) SaveAndApplyConfig(ctx context.Context, config *apimodels.PostableUserConfig) error { return nil } -// TODO: save the new configuration hash in memory. func (fam *RemotePrimaryForkedAlertmanager) SaveAndApplyDefaultConfig(ctx context.Context) error { + if err := fam.remote.SaveAndApplyDefaultConfig(ctx); err != nil { + return fmt.Errorf("failed to send the default configuration to the remote Alertmanager: %w", err) + } + + if err := fam.internal.SaveAndApplyDefaultConfig(ctx); err != nil { + fam.log.Error("Error applying the default configuration to the internal Alertmanager", "err", err) + } return nil } From c77ab53819256f8239777eb47ffa964637f9344b Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 23 Apr 2024 14:37:10 +0200 Subject: [PATCH 049/222] Alerting: implement SaveAndApplyConfig in the remote Alertmanager struct (#84642) * implement SaveAndApplyConfig in the remote Alertmanager struct * remove ID from CreateGrafanaAlertmanagerConfig call * decrypt, test that we decrypt, refactor * fix duplicated declaration in test * rephrase comment, remove unnecessary conversion to slice of bytes * fix test --- pkg/services/ngalert/remote/alertmanager.go | 15 +++++++- .../ngalert/remote/alertmanager_test.go | 34 +++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/pkg/services/ngalert/remote/alertmanager.go b/pkg/services/ngalert/remote/alertmanager.go index eda40a57f16..6862fba4e99 100644 --- a/pkg/services/ngalert/remote/alertmanager.go +++ b/pkg/services/ngalert/remote/alertmanager.go @@ -269,8 +269,21 @@ func (am *Alertmanager) CompareAndSendState(ctx context.Context) error { return nil } +// SaveAndApplyConfig decrypts and sends a configuration to the remote Alertmanager. func (am *Alertmanager) SaveAndApplyConfig(ctx context.Context, cfg *apimodels.PostableUserConfig) error { - return nil + // Get the hash for the encrypted configuration. + rawCfg, err := json.Marshal(cfg) + if err != nil { + return err + } + hash := fmt.Sprintf("%x", md5.Sum(rawCfg)) + + // Decrypt and send. + decrypted, err := am.decryptConfiguration(ctx, cfg) + if err != nil { + return err + } + return am.sendConfiguration(ctx, decrypted, hash, time.Now().Unix(), false) } // SaveAndApplyDefaultConfig sends the default Grafana Alertmanager configuration to the remote Alertmanager. diff --git a/pkg/services/ngalert/remote/alertmanager_test.go b/pkg/services/ngalert/remote/alertmanager_test.go index 9d812551331..188889fd56b 100644 --- a/pkg/services/ngalert/remote/alertmanager_test.go +++ b/pkg/services/ngalert/remote/alertmanager_test.go @@ -145,7 +145,7 @@ func TestApplyConfig(t *testing.T) { // The encrypted configuration should be different than the one we will send. encryptedConfig, err := json.Marshal(c) require.NoError(t, err) - require.NotEqual(t, testGrafanaConfig, encryptedConfig) + require.NotEqual(t, testGrafanaConfigWithSecret, encryptedConfig) // ApplyConfig performs a readiness check at startup. // A non-200 response should result in an error. @@ -316,7 +316,7 @@ func TestIntegrationRemoteAlertmanagerConfiguration(t *testing.T) { require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", notifier.SilencesFilename, testSilence1)) require.NoError(t, store.Set(ctx, cfg.OrgID, "alertmanager", notifier.NotificationLogFilename, testNflog1)) - secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) + secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(db.InitTestDB(t))) m := metrics.NewRemoteAlertmanagerMetrics(prometheus.NewRegistry()) am, err := NewAlertmanager(cfg, fstore, secretsService.Decrypt, defaultGrafanaConfig, m) require.NoError(t, err) @@ -386,6 +386,36 @@ func TestIntegrationRemoteAlertmanagerConfiguration(t *testing.T) { require.Equal(t, encodedFullState, state.State) } + // `SaveAndApplyConfig` is called whenever a user manually changes the Alertmanager configuration. + // Calling this method should decrypt and send a configuration to the remote Alertmanager. + { + postableCfg, err := notifier.Load([]byte(testGrafanaConfigWithSecret)) + require.NoError(t, err) + err = notifier.EncryptReceiverConfigs(postableCfg.AlertmanagerConfig.Receivers, func(ctx context.Context, payload []byte) ([]byte, error) { + return secretsService.Encrypt(ctx, payload, secrets.WithoutScope()) + }) + require.NoError(t, err) + + // The encrypted configuration should be different than the one we will send. + encryptedConfig, err := json.Marshal(postableCfg) + require.NoError(t, err) + require.NotEqual(t, testGrafanaConfigWithSecret, encryptedConfig) + + // Call `SaveAndApplyConfig` with the encrypted configuration. + require.NoError(t, err) + require.NoError(t, am.SaveAndApplyConfig(ctx, postableCfg)) + + // Check that the configuration was uploaded to the remote Alertmanager. + config, err := am.mimirClient.GetGrafanaAlertmanagerConfig(ctx) + require.NoError(t, err) + got, err := json.Marshal(config.GrafanaAlertmanagerConfig) + require.NoError(t, err) + + require.JSONEq(t, testGrafanaConfigWithSecret, string(got)) + require.Equal(t, fmt.Sprintf("%x", md5.Sum(encryptedConfig)), config.Hash) + require.False(t, config.Default) + } + // `SaveAndApplyDefaultConfig` should send the default Alertmanager configuration to the remote Alertmanager. { require.NoError(t, am.SaveAndApplyDefaultConfig(ctx)) From 68564b1940d496ab97ad9442e70c9919c88af505 Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Tue, 23 Apr 2024 14:38:31 +0200 Subject: [PATCH 050/222] Alerting: Gops labels integration (#85467) * Show list of labels on the alert rule form in a more visual way, and use a modal for managing them * fix test * Fix more tests * Show orange badge when no labels are selected * Remove unused datasource property in LabelsField * Remove unused div and add comment * Use button instead of icon for editing labels * Use subform for labels * Move logic fetching labels from different places in a separate hook * Fix tests * remove unused getLabelInput const in test * Add ellispis and tooltip for long labels and move labels list in modal to the bottom * Use text instead of badge when no using labels * Fix tests after adding ellipsis and tooltip to the labels * simplify styles * Fix fetching values from gops when new key is used * Address pr review comments * Address pr review comments part2 * Fix tag on rtkq * Remove color for no labels selected text * Disable already used keys in the labels sub form * Fix typo * use the UseFieldArrayRemove type from react-hook-form * Update some styles and text in the labels modal * Address some review comments (nits) * Address review comments part1 * Move logic getting labels in useCombinedLabels hook --------- Co-authored-by: Gilles De Mey --- .../alerting/unified/AlertGroups.test.tsx | 4 +- .../alerting/unified/CloneRuleEditor.test.tsx | 28 +- .../unified/RuleEditorCloudRules.test.tsx | 13 +- .../unified/RuleEditorExisting.test.tsx | 12 +- .../unified/RuleEditorGrafanaRules.test.tsx | 16 +- .../unified/RuleEditorRecordingRule.test.tsx | 19 +- .../alerting/unified/RuleList.test.tsx | 13 +- .../alerting/unified/api/alertingApi.ts | 1 + .../alerting/unified/api/labelsApi.ts | 31 ++ .../unified/components/AlertLabelDropdown.tsx | 15 +- .../alerting/unified/components/Label.tsx | 28 +- .../receivers/form/GenerateAlertDataModal.tsx | 4 +- .../receivers/form/TestContactPointModal.tsx | 2 +- .../receivers/grafanaAppReceivers/types.ts | 1 + .../rule-editor/LabelsField.test.tsx | 130 ----- .../components/rule-editor/LabelsField.tsx | 339 ------------ .../rule-editor/NotificationsStep.tsx | 28 +- .../rule-editor/labels/LabelsButtons.tsx | 33 ++ .../rule-editor/labels/LabelsEditorModal.tsx | 27 + .../rule-editor/labels/LabelsField.test.tsx | 176 +++++++ .../rule-editor/labels/LabelsField.tsx | 489 ++++++++++++++++++ .../rule-editor/labels/LabelsFieldInForm.tsx | 51 ++ public/app/features/alerting/unified/mocks.ts | 20 + .../alerting/unified/types/pluginBridges.ts | 1 + 24 files changed, 947 insertions(+), 534 deletions(-) create mode 100644 public/app/features/alerting/unified/api/labelsApi.ts delete mode 100644 public/app/features/alerting/unified/components/rule-editor/LabelsField.test.tsx delete mode 100644 public/app/features/alerting/unified/components/rule-editor/LabelsField.tsx create mode 100644 public/app/features/alerting/unified/components/rule-editor/labels/LabelsButtons.tsx create mode 100644 public/app/features/alerting/unified/components/rule-editor/labels/LabelsEditorModal.tsx create mode 100644 public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx create mode 100644 public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.tsx create mode 100644 public/app/features/alerting/unified/components/rule-editor/labels/LabelsFieldInForm.tsx diff --git a/public/app/features/alerting/unified/AlertGroups.test.tsx b/public/app/features/alerting/unified/AlertGroups.test.tsx index 5771a296785..23a848d50f0 100644 --- a/public/app/features/alerting/unified/AlertGroups.test.tsx +++ b/public/app/features/alerting/unified/AlertGroups.test.tsx @@ -93,7 +93,9 @@ describe('AlertGroups', () => { expect(groups).toHaveLength(2); expect(groups[0]).toHaveTextContent('No grouping'); - expect(groups[1]).toHaveTextContent('severitywarning regionUS-Central'); + const labels = byTestId('label-value').getAll(); + expect(labels[0]).toHaveTextContent('severitywarning'); + expect(labels[1]).toHaveTextContent('regionUS-Central'); await userEvent.click(ui.groupCollapseToggle.get(groups[0])); expect(ui.groupTable.get()).toBeDefined(); diff --git a/public/app/features/alerting/unified/CloneRuleEditor.test.tsx b/public/app/features/alerting/unified/CloneRuleEditor.test.tsx index c2f77b60a46..61fef735f65 100644 --- a/public/app/features/alerting/unified/CloneRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/CloneRuleEditor.test.tsx @@ -21,8 +21,9 @@ import { import { cloneRuleDefinition, CloneRuleEditor } from './CloneRuleEditor'; import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor'; -import { mockSearchApi } from './mockApi'; +import { mockApi, mockSearchApi } from './mockApi'; import { + labelsPluginMetaMock, mockDataSource, MockDataSourceSrv, mockRulerAlertingRule, @@ -138,6 +139,7 @@ const amConfig: AlertManagerCortexConfig = { template_files: {}, }; +mockApi(server).plugins.getPluginSettings({ ...labelsPluginMetaMock, enabled: false }); describe('CloneRuleEditor', function () { describe('Grafana-managed rules', function () { it('should populate form values from the existing alert rule', async function () { @@ -174,8 +176,16 @@ describe('CloneRuleEditor', function () { expect(ui.inputs.name.get()).toHaveValue('First Grafana Rule (copy)'); expect(ui.inputs.folderContainer.get()).toHaveTextContent('folder-one'); expect(ui.inputs.group.get()).toHaveTextContent('group1'); - expect(ui.inputs.labelValue(0).get()).toHaveTextContent('critical'); - expect(ui.inputs.labelValue(1).get()).toHaveTextContent('nasa'); + expect( + byRole('listitem', { + name: 'severity: critical', + }).get() + ).toBeInTheDocument(); + expect( + byRole('listitem', { + name: 'region: nasa', + }).get() + ).toBeInTheDocument(); expect(ui.inputs.annotationValue(0).get()).toHaveTextContent('This is a very important alert rule'); }); }); @@ -244,8 +254,16 @@ describe('CloneRuleEditor', function () { expect(ui.inputs.expr.get()).toHaveValue('vector(1) > 0'); expect(ui.inputs.namespace.get()).toHaveTextContent('namespace-one'); expect(ui.inputs.group.get()).toHaveTextContent('group1'); - expect(ui.inputs.labelValue(0).get()).toHaveTextContent('critical'); - expect(ui.inputs.labelValue(1).get()).toHaveTextContent('nasa'); + expect( + byRole('listitem', { + name: 'severity: critical', + }).get() + ).toBeInTheDocument(); + expect( + byRole('listitem', { + name: 'region: nasa', + }).get() + ).toBeInTheDocument(); expect(ui.inputs.annotationValue(0).get()).toHaveTextContent('This is a very important alert rule'); }); }); diff --git a/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx b/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx index 061b5dcbdb4..e17a7c7a2bf 100644 --- a/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx @@ -1,4 +1,4 @@ -import { screen, waitFor, waitForElementToBeRemoved, within } from '@testing-library/react'; +import { screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { renderRuleEditor, ui } from 'test/helpers/alertingRuleEditor'; @@ -13,7 +13,7 @@ import { searchFolders } from '../../manage-dashboards/state/actions'; import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler'; import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor'; import { mockApi, mockFeatureDiscoveryApi, setupMswServer } from './mockApi'; -import { grantUserPermissions, mockDataSource } from './mocks'; +import { grantUserPermissions, labelsPluginMetaMock, mockDataSource } from './mocks'; import { defaultAlertmanagerChoiceResponse, emptyExternalAlertmanagersResponse, @@ -58,6 +58,7 @@ mockFeatureDiscoveryApi(server).discoverDsFeatures(dataSources.default, buildInf mockAlertmanagerChoiceResponse(server, defaultAlertmanagerChoiceResponse); mockAlertmanagersResponse(server, emptyExternalAlertmanagersResponse); mockApi(server).eval({ results: {} }); +mockApi(server).plugins.getPluginSettings({ ...labelsPluginMetaMock, enabled: false }); // these tests are rather slow because we have to wait for various API calls and mocks to be called // and wait for the UI to be in particular states, drone seems to time out quite often so @@ -76,8 +77,6 @@ const mocks = { }, }; -const getLabelInput = (selector: HTMLElement) => within(selector).getByRole('combobox'); - describe('RuleEditor cloud', () => { beforeEach(() => { jest.clearAllMocks(); @@ -158,9 +157,6 @@ describe('RuleEditor cloud', () => { // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed await user.click(ui.buttons.addLabel.get()); - await user.type(getLabelInput(ui.inputs.labelKey(0).get()), 'severity{enter}'); - await user.type(getLabelInput(ui.inputs.labelValue(0).get()), 'warn{enter}'); - // save and check what was sent to backend await user.click(ui.buttons.saveAndExit.get()); await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled()); @@ -173,9 +169,10 @@ describe('RuleEditor cloud', () => { { alert: 'my great new rule', annotations: { description: 'some description', summary: 'some summary' }, - labels: { severity: 'warn' }, expr: 'up == 1', for: '1m', + labels: {}, + keep_firing_for: undefined, }, ], } diff --git a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx b/public/app/features/alerting/unified/RuleEditorExisting.test.tsx index ee8a14a749f..ec49c212dfa 100644 --- a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorExisting.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor, within } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { Route } from 'react-router-dom'; @@ -18,7 +18,7 @@ import RuleEditor from './RuleEditor'; import { discoverFeatures } from './api/buildInfo'; import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler'; import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor'; -import { grantUserPermissions, mockDataSource, MockDataSourceSrv, mockFolder } from './mocks'; +import { MockDataSourceSrv, grantUserPermissions, mockDataSource, mockFolder } from './mocks'; import { fetchRulerRulesIfNotFetchedYet } from './state/actions'; import * as config from './utils/config'; import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; @@ -73,7 +73,6 @@ function renderRuleEditor(identifier?: string) { ); } -const getLabelInput = (selector: HTMLElement) => within(selector).getByRole('combobox'); describe('RuleEditor grafana managed rules', () => { beforeEach(() => { jest.clearAllMocks(); @@ -187,10 +186,6 @@ describe('RuleEditor grafana managed rules', () => { await userEvent.type(screen.getByPlaceholderText('Enter custom annotation name...'), 'custom'); await userEvent.type(screen.getByPlaceholderText('Enter custom annotation content...'), 'value'); - //add a label - await userEvent.type(getLabelInput(ui.inputs.labelKey(2).get()), 'custom{enter}'); - await userEvent.type(getLabelInput(ui.inputs.labelValue(2).get()), 'value{enter}'); - // save and check what was sent to backend await userEvent.click(ui.buttons.save.get()); await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled()); @@ -207,13 +202,14 @@ describe('RuleEditor grafana managed rules', () => { rules: [ { annotations: { description: 'some description', summary: 'some summary', custom: 'value' }, - labels: { severity: 'warn', team: 'the a-team', custom: 'value' }, + labels: { severity: 'warn', team: 'the a-team' }, for: '1m', grafana_alert: { uid, condition: 'B', data: getDefaultQueries(), exec_err_state: GrafanaAlertStateDecision.Error, + notification_settings: undefined, is_paused: false, no_data_state: 'NoData', title: 'my great new rule', diff --git a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx index cdd52a78046..ebfdcb91742 100644 --- a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx @@ -1,9 +1,10 @@ -import { screen, waitFor, waitForElementToBeRemoved, within } from '@testing-library/react'; -import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; +import { screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import { renderRuleEditor, ui } from 'test/helpers/alertingRuleEditor'; import { clickSelectOption } from 'test/helpers/selectOptionInTest'; import { byRole } from 'testing-library-selector'; +import 'whatwg-fetch'; import { setDataSourceSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; @@ -68,7 +69,6 @@ const mocks = { const server = setupMswServer(); -const getLabelInput = (selector: HTMLElement) => within(selector).getByRole('combobox'); describe('RuleEditor grafana managed rules', () => { beforeEach(() => { mockApi(server).eval({ results: {} }); @@ -196,13 +196,6 @@ describe('RuleEditor grafana managed rules', () => { await clickSelectOption(groupInput, 'group1'); await userEvent.type(ui.inputs.annotationValue(1).get(), 'some description'); - // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - await userEvent.click(ui.buttons.addLabel.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); - - await userEvent.type(getLabelInput(ui.inputs.labelKey(0).get()), 'severity{enter}'); - await userEvent.type(getLabelInput(ui.inputs.labelValue(0).get()), 'warn{enter}'); - //8 segons - // save and check what was sent to backend await userEvent.click(ui.buttons.saveAndExit.get()); // 9seg @@ -217,7 +210,7 @@ describe('RuleEditor grafana managed rules', () => { rules: [ { annotations: { description: 'some description' }, - labels: { severity: 'warn' }, + labels: {}, for: '1m', grafana_alert: { condition: 'B', @@ -226,6 +219,7 @@ describe('RuleEditor grafana managed rules', () => { is_paused: false, no_data_state: 'NoData', title: 'my great new rule', + notification_settings: undefined, }, }, ], diff --git a/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx b/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx index 63f6ea53268..bbb0aead53e 100644 --- a/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx @@ -1,9 +1,10 @@ -import { screen, waitFor, waitForElementToBeRemoved, within } from '@testing-library/react'; -import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; +import { screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import { renderRuleEditor, ui } from 'test/helpers/alertingRuleEditor'; import { clickSelectOption } from 'test/helpers/selectOptionInTest'; import { byText } from 'testing-library-selector'; +import 'whatwg-fetch'; import { setDataSourceSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; @@ -16,7 +17,7 @@ import { searchFolders } from '../../manage-dashboards/state/actions'; import { discoverFeatures } from './api/buildInfo'; import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler'; import { RecordingRuleEditorProps } from './components/rule-editor/RecordingRuleEditor'; -import { grantUserPermissions, mockDataSource, MockDataSourceSrv } from './mocks'; +import { MockDataSourceSrv, grantUserPermissions, labelsPluginMetaMock, mockDataSource } from './mocks'; import { fetchRulerRulesIfNotFetchedYet } from './state/actions'; import * as config from './utils/config'; @@ -92,9 +93,9 @@ const mocks = { }, }; -const getLabelInput = (selector: HTMLElement) => within(selector).getByRole('combobox'); - const server = setupMswServer(); +mockApi(server).plugins.getPluginSettings({ ...labelsPluginMetaMock, enabled: false }); +mockApi(server).eval({ results: { A: { frames: [] } } }); describe('RuleEditor recording rules', () => { beforeEach(() => { @@ -162,12 +163,6 @@ describe('RuleEditor recording rules', () => { await userEvent.type(await ui.inputs.expr.find(), 'up == 1'); - // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - await userEvent.click(ui.buttons.addLabel.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); - - await userEvent.type(getLabelInput(ui.inputs.labelKey(1).get()), 'team{enter}'); - await userEvent.type(getLabelInput(ui.inputs.labelValue(1).get()), 'the a-team{enter}'); - // try to save, find out that recording rule name is invalid await userEvent.click(ui.buttons.saveAndExit.get()); await waitFor(() => @@ -194,7 +189,7 @@ describe('RuleEditor recording rules', () => { rules: [ { record: 'my:great:new:recording:rule', - labels: { team: 'the a-team' }, + labels: {}, expr: 'up == 1', }, ], diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 06d1e9c8256..10d570e30f9 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -440,8 +440,10 @@ describe('RuleList', () => { await userEvent.click(ui.ruleCollapseToggle.get(ruleRows[1])); const ruleDetails = ui.expandedContent.get(ruleRows[1]); + const labels = byTestId('label-value').getAll(ruleDetails); + expect(labels[0]).toHaveTextContent('severitywarning'); + expect(labels[1]).toHaveTextContent('foobar'); - expect(ruleDetails).toHaveTextContent('Labels severitywarning foobar'); expect(ruleDetails).toHaveTextContent('Expressiontopk ( 5 , foo ) [ 5m ]'); expect(ruleDetails).toHaveTextContent('messagegreat alert'); expect(ruleDetails).toHaveTextContent('Matching instances'); @@ -452,8 +454,8 @@ describe('RuleList', () => { const instanceRows = byTestId('row').getAll(instancesTable); expect(instanceRows).toHaveLength(2); - expect(instanceRows![0]).toHaveTextContent('Firing foobar severitywarning2021-03-18 08:47:05'); - expect(instanceRows![1]).toHaveTextContent('Firing foobaz severityerror2021-03-18 08:47:05'); + expect(instanceRows![0]).toHaveTextContent('Firingfoobarseveritywarning2021-03-18 08:47:05'); + expect(instanceRows![1]).toHaveTextContent('Firingfoobazseverityerror2021-03-18 08:47:05'); // expand details of an instance await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0])); @@ -593,8 +595,9 @@ describe('RuleList', () => { await userEvent.click(ui.ruleCollapseToggle.get(ruleRows[0])); const ruleDetails = ui.expandedContent.get(ruleRows[0]); - - expect(ruleDetails).toHaveTextContent('Labels severitywarning foobar'); + const labels = byTestId('label-value').getAll(ruleDetails); + expect(labels[0]).toHaveTextContent('severitywarning'); + expect(labels[1]).toHaveTextContent('foobar'); // Check for different label matchers await userEvent.clear(filterInput); diff --git a/public/app/features/alerting/unified/api/alertingApi.ts b/public/app/features/alerting/unified/api/alertingApi.ts index 1efad56dfea..fce6b5b21f3 100644 --- a/public/app/features/alerting/unified/api/alertingApi.ts +++ b/public/app/features/alerting/unified/api/alertingApi.ts @@ -38,6 +38,7 @@ export const alertingApi = createApi({ 'OnCallIntegrations', 'OrgMigrationState', 'DataSourceSettings', + 'GrafanaLabels', 'CombinedAlertRule', ], endpoints: () => ({}), diff --git a/public/app/features/alerting/unified/api/labelsApi.ts b/public/app/features/alerting/unified/api/labelsApi.ts new file mode 100644 index 00000000000..68f81d11a94 --- /dev/null +++ b/public/app/features/alerting/unified/api/labelsApi.ts @@ -0,0 +1,31 @@ +import { SupportedPlugin } from '../types/pluginBridges'; + +import { alertingApi } from './alertingApi'; + +export interface LabelItem { + id: string; + name: string; + prescribed: boolean; +} + +export interface LabelKeyAndValues { + labelKey: LabelItem; + values: LabelItem[]; +} + +export const labelsApi = alertingApi.injectEndpoints({ + endpoints: (build) => ({ + getLabels: build.query({ + query: () => ({ + url: `/api/plugins/${SupportedPlugin.Labels}/resources/v1/labels/keys`, + }), + providesTags: ['GrafanaLabels'], + }), + getLabelValues: build.query({ + query: ({ key }) => ({ + url: `/api/plugins/${SupportedPlugin.Labels}/resources/v1/labels/name/${key}`, + }), + providesTags: ['GrafanaLabels'], + }), + }), +}); diff --git a/public/app/features/alerting/unified/components/AlertLabelDropdown.tsx b/public/app/features/alerting/unified/components/AlertLabelDropdown.tsx index ecd67bf5156..9b9faf828ee 100644 --- a/public/app/features/alerting/unified/components/AlertLabelDropdown.tsx +++ b/public/app/features/alerting/unified/components/AlertLabelDropdown.tsx @@ -1,8 +1,9 @@ +import { css } from '@emotion/css'; import React, { FC } from 'react'; import { createFilter, GroupBase, OptionsOrGroups } from 'react-select'; import { SelectableValue } from '@grafana/data'; -import { Field, Select } from '@grafana/ui'; +import { Field, Select, useStyles2 } from '@grafana/ui'; export interface AlertLabelDropdownProps { onChange: (newValue: SelectableValue) => void; @@ -25,7 +26,7 @@ function customFilter(opt: SelectableValue, searchQuery: string) { const handleIsValidNewOption = ( inputValue: string, - value: SelectableValue | null, + _: SelectableValue | null, options: OptionsOrGroups, GroupBase>> ) => { const exactValueExists = options.some((el) => el.label === inputValue); @@ -34,10 +35,12 @@ const handleIsValidNewOption = ( }; const AlertLabelDropdown: FC = React.forwardRef( - function labelPicker({ onChange, options, defaultValue, type, onOpenMenu = () => {} }, ref) { + function LabelPicker({ onChange, options, defaultValue, type, onOpenMenu = () => {} }, ref) { + const styles = useStyles2(getStyles); + return (
- + placeholder={`Choose ${type}`} width={29} @@ -59,4 +62,8 @@ const AlertLabelDropdown: FC = React.forwardRef ({ + resetMargin: css({ marginBottom: 0 }), +}); + export default AlertLabelDropdown; diff --git a/public/app/features/alerting/unified/components/Label.tsx b/public/app/features/alerting/unified/components/Label.tsx index 37177f3801c..66505aefa3e 100644 --- a/public/app/features/alerting/unified/components/Label.tsx +++ b/public/app/features/alerting/unified/components/Label.tsx @@ -3,7 +3,7 @@ import React, { ReactNode } from 'react'; import tinycolor2 from 'tinycolor2'; import { GrafanaTheme2, IconName } from '@grafana/data'; -import { Icon, useStyles2, Stack } from '@grafana/ui'; +import { Icon, Stack, useStyles2 } from '@grafana/ui'; export type LabelSize = 'md' | 'sm'; @@ -21,14 +21,23 @@ const Label = ({ label, value, icon, color, size = 'md' }: Props) => { const ariaLabel = `${label}: ${value}`; return ( -
+
- {icon && } {label ?? ''} + {icon && } + {label && ( + + {label ?? ''} + + )}
-
{value}
+ {value && ( +
+ {value} +
+ )}
); @@ -59,6 +68,12 @@ const getStyles = (theme: GrafanaTheme2, color?: string, size?: string) => { border-radius: ${theme.shape.borderRadius(2)}; `, + labelText: css({ + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + maxWidth: '300px', + }), label: css` display: flex; align-items: center; @@ -80,6 +95,11 @@ const getStyles = (theme: GrafanaTheme2, color?: string, size?: string) => { border-left: none; border-top-right-radius: ${theme.shape.borderRadius(2)}; border-bottom-right-radius: ${theme.shape.borderRadius(2)}; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 300px; `, }; }; 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 4fb7abb1bb3..45e99ace897 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx @@ -5,12 +5,12 @@ import React, { useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { GrafanaTheme2 } from '@grafana/data'; -import { Button, Card, Modal, RadioButtonGroup, useStyles2, Stack } from '@grafana/ui'; +import { Button, Card, Modal, RadioButtonGroup, Stack, useStyles2 } from '@grafana/ui'; import { TestTemplateAlert } from 'app/plugins/datasource/alertmanager/types'; import { KeyValueField } from '../../../api/templateApi'; import AnnotationsStep from '../../rule-editor/AnnotationsStep'; -import LabelsField from '../../rule-editor/LabelsField'; +import LabelsField from '../../rule-editor/labels/LabelsField'; interface Props { isOpen: boolean; diff --git a/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx b/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx index d951f444285..81c71479df0 100644 --- a/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx @@ -9,7 +9,7 @@ import { Annotations, Labels } from 'app/types/unified-alerting-dto'; import { defaultAnnotations } from '../../../utils/constants'; import AnnotationsStep from '../../rule-editor/AnnotationsStep'; -import LabelsField from '../../rule-editor/LabelsField'; +import LabelsField from '../../rule-editor/labels/LabelsField'; interface Props { isOpen: boolean; diff --git a/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/types.ts b/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/types.ts index 5da23618271..6b65cc39322 100644 --- a/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/types.ts +++ b/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/types.ts @@ -11,4 +11,5 @@ export const GRAFANA_APP_RECEIVERS_SOURCE_IMAGE: Record [SupportedPlugin.Incident]: '', [SupportedPlugin.MachineLearning]: '', + [SupportedPlugin.Labels]: '', }; diff --git a/public/app/features/alerting/unified/components/rule-editor/LabelsField.test.tsx b/public/app/features/alerting/unified/components/rule-editor/LabelsField.test.tsx deleted file mode 100644 index 2dd636d6187..00000000000 --- a/public/app/features/alerting/unified/components/rule-editor/LabelsField.test.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { render, screen, waitFor, within } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { FormProvider, useForm } from 'react-hook-form'; -import { Provider } from 'react-redux'; - -import { configureStore } from 'app/store/configureStore'; - -import LabelsField from './LabelsField'; - -const labels = [ - { key: 'key1', value: 'value1' }, - { key: 'key2', value: 'value2' }, -]; - -const FormProviderWrapper = ({ children }: React.PropsWithChildren<{}>) => { - const methods = useForm({ defaultValues: { labels } }); - return {children}; -}; - -function renderAlertLabels(dataSourceName?: string) { - const store = configureStore({}); - - render( - - {dataSourceName ? : } - , - { wrapper: FormProviderWrapper } - ); -} - -describe('LabelsField with suggestions', () => { - it('Should display two dropdowns with the existing labels', async () => { - renderAlertLabels('grafana'); - - await waitFor(() => expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(2)); - - expect(screen.getByTestId('label-key-0').textContent).toBe('key1'); - expect(screen.getByTestId('label-key-1').textContent).toBe('key2'); - - expect(screen.getAllByTestId('alertlabel-value-picker')).toHaveLength(2); - - expect(screen.getByTestId('label-value-0').textContent).toBe('value1'); - expect(screen.getByTestId('label-value-1').textContent).toBe('value2'); - }); - - it('Should delete a key-value combination', async () => { - renderAlertLabels('grafana'); - - await waitFor(() => expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(2)); - - expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(2); - expect(screen.getAllByTestId('alertlabel-value-picker')).toHaveLength(2); - - await userEvent.click(screen.getByTestId('delete-label-1')); - - expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(1); - expect(screen.getAllByTestId('alertlabel-value-picker')).toHaveLength(1); - }); - - it('Should add new key-value dropdowns', async () => { - renderAlertLabels('grafana'); - - await waitFor(() => expect(screen.getByText('Add label')).toBeVisible()); - await userEvent.click(screen.getByText('Add label')); - - expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(3); - - expect(screen.getByTestId('label-key-0').textContent).toBe('key1'); - expect(screen.getByTestId('label-key-1').textContent).toBe('key2'); - expect(screen.getByTestId('label-key-2').textContent).toBe('Choose key'); - - expect(screen.getAllByTestId('alertlabel-value-picker')).toHaveLength(3); - - expect(screen.getByTestId('label-value-0').textContent).toBe('value1'); - expect(screen.getByTestId('label-value-1').textContent).toBe('value2'); - expect(screen.getByTestId('label-value-2').textContent).toBe('Choose value'); - }); - - it('Should be able to write new keys and values using the dropdowns', async () => { - renderAlertLabels('grafana'); - - await waitFor(() => expect(screen.getByText('Add label')).toBeVisible()); - await userEvent.click(screen.getByText('Add label')); - - const LastKeyDropdown = within(screen.getByTestId('label-key-2')); - const LastValueDropdown = within(screen.getByTestId('label-value-2')); - - await userEvent.type(LastKeyDropdown.getByRole('combobox'), 'key3{enter}'); - await userEvent.type(LastValueDropdown.getByRole('combobox'), 'value3{enter}'); - - expect(screen.getByTestId('label-key-2').textContent).toBe('key3'); - expect(screen.getByTestId('label-value-2').textContent).toBe('value3'); - }); - it('Should be able to write new keys and values using the dropdowns, case sensitive', async () => { - renderAlertLabels('grafana'); - - await waitFor(() => expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(2)); - expect(screen.getByTestId('label-key-0').textContent).toBe('key1'); - expect(screen.getByTestId('label-key-1').textContent).toBe('key2'); - expect(screen.getByTestId('label-value-0').textContent).toBe('value1'); - expect(screen.getByTestId('label-value-1').textContent).toBe('value2'); - - const LastKeyDropdown = within(screen.getByTestId('label-key-1')); - const LastValueDropdown = within(screen.getByTestId('label-value-1')); - - await userEvent.type(LastKeyDropdown.getByRole('combobox'), 'KEY2{enter}'); - expect(screen.getByTestId('label-key-0').textContent).toBe('key1'); - expect(screen.getByTestId('label-key-1').textContent).toBe('KEY2'); - - await userEvent.type(LastValueDropdown.getByRole('combobox'), 'VALUE2{enter}'); - expect(screen.getByTestId('label-value-0').textContent).toBe('value1'); - expect(screen.getByTestId('label-value-1').textContent).toBe('VALUE2'); - }); -}); - -describe('LabelsField without suggestions', () => { - it('Should display two inputs without label suggestions', async () => { - renderAlertLabels(); - - await waitFor(() => expect(screen.getAllByTestId('alertlabel-input-wrapper')).toHaveLength(2)); - expect(screen.queryAllByTestId('alertlabel-key-picker')).toHaveLength(0); - - expect(screen.getByTestId('label-key-0')).toHaveValue('key1'); - expect(screen.getByTestId('label-key-1')).toHaveValue('key2'); - - expect(screen.getByTestId('label-value-0')).toHaveValue('value1'); - expect(screen.getByTestId('label-value-1')).toHaveValue('value2'); - }); -}); diff --git a/public/app/features/alerting/unified/components/rule-editor/LabelsField.tsx b/public/app/features/alerting/unified/components/rule-editor/LabelsField.tsx deleted file mode 100644 index 69f14d36315..00000000000 --- a/public/app/features/alerting/unified/components/rule-editor/LabelsField.tsx +++ /dev/null @@ -1,339 +0,0 @@ -import { css, cx } from '@emotion/css'; -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { useFieldArray, UseFieldArrayAppend, useFormContext, Controller } from 'react-hook-form'; - -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { Button, Field, InlineLabel, Input, LoadingPlaceholder, Stack, Text, useStyles2 } from '@grafana/ui'; -import { useDispatch } from 'app/types'; - -import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; -import { fetchRulerRulesIfNotFetchedYet } from '../../state/actions'; -import { RuleFormValues } from '../../types/rule-form'; -import AlertLabelDropdown from '../AlertLabelDropdown'; - -import { NeedHelpInfo } from './NeedHelpInfo'; - -interface Props { - className?: string; - dataSourceName?: string | null; -} - -const useGetCustomLabels = (dataSourceName: string): { loading: boolean; labelsByKey: Record> } => { - const dispatch = useDispatch(); - - useEffect(() => { - dispatch(fetchRulerRulesIfNotFetchedYet(dataSourceName)); - }, [dispatch, dataSourceName]); - - const rulerRuleRequests = useUnifiedAlertingSelector((state) => state.rulerRules); - const rulerRequest = rulerRuleRequests[dataSourceName]; - - const labelsByKeyResult = useMemo>>(() => { - const labelsByKey: Record> = {}; - - const rulerRulesConfig = rulerRequest?.result; - if (!rulerRulesConfig) { - return labelsByKey; - } - - const allRules = Object.values(rulerRulesConfig) - .flatMap((groups) => groups) - .flatMap((group) => group.rules); - - allRules.forEach((rule) => { - if (rule.labels) { - Object.entries(rule.labels).forEach(([key, value]) => { - if (!value) { - return; - } - - const labelEntry = labelsByKey[key]; - if (labelEntry) { - labelEntry.add(value); - } else { - labelsByKey[key] = new Set([value]); - } - }); - } - }); - - return labelsByKey; - }, [rulerRequest]); - - return { loading: rulerRequest?.loading, labelsByKey: labelsByKeyResult }; -}; - -function mapLabelsToOptions(items: Iterable = []): Array> { - return Array.from(items, (item) => ({ label: item, value: item })); -} - -const RemoveButton: FC<{ - remove: (index?: number | number[] | undefined) => void; - className: string; - index: number; -}> = ({ remove, className, index }) => ( - -); - -const LabelsWithSuggestions: FC<{ dataSourceName: string }> = ({ dataSourceName }) => { - const styles = useStyles2(getStyles); - const { - control, - watch, - formState: { errors }, - } = useFormContext(); - - const labels = watch('labels'); - const { fields, remove, append } = useFieldArray({ control, name: 'labels' }); - - const { loading, labelsByKey } = useGetCustomLabels(dataSourceName); - - const [selectedKey, setSelectedKey] = useState(''); - - const keys = useMemo(() => { - return mapLabelsToOptions(Object.keys(labelsByKey)); - }, [labelsByKey]); - - const getValuesForLabel = useCallback( - (key: string) => { - return mapLabelsToOptions(labelsByKey[key]); - }, - [labelsByKey] - ); - - const values = useMemo(() => { - return getValuesForLabel(selectedKey); - }, [selectedKey, getValuesForLabel]); - - return ( - <> - {loading && } - {!loading && ( - - {fields.map((field, index) => { - return ( -
-
- - { - return ( - { - onChange(newValue.value); - setSelectedKey(newValue.value); - }} - type="key" - /> - ); - }} - /> - - = - - { - return ( - { - onChange(newValue.value); - }} - onOpenMenu={() => { - setSelectedKey(labels[index].key); - }} - type="value" - /> - ); - }} - /> - - - -
-
- ); - })} - -
- )} - - ); -}; - -const LabelsWithoutSuggestions: FC = () => { - const styles = useStyles2(getStyles); - const { - register, - control, - watch, - formState: { errors }, - } = useFormContext(); - - const labels = watch('labels'); - const { fields, remove, append } = useFieldArray({ control, name: 'labels' }); - - return ( - <> - {fields.map((field, index) => { - return ( -
-
- - - - = - - - - -
-
- ); - })} - - - ); -}; - -const LabelsField: FC = ({ dataSourceName }) => { - const styles = useStyles2(getStyles); - - return ( -
- - Labels - - - Add labels to your rule for searching, silencing, or routing to a notification policy. - - - - -
- {dataSourceName ? : } -
- ); -}; - -const getStyles = (theme: GrafanaTheme2) => { - return { - icon: css({ - marginRight: theme.spacing(0.5), - }), - flexColumn: css({ - display: 'flex', - flexDirection: 'column', - }), - flexRow: css({ - display: 'flex', - flexDirection: 'row', - justifyContent: 'flex-start', - '& + button': { - marginLeft: theme.spacing(0.5), - }, - }), - deleteLabelButton: css({ - marginLeft: theme.spacing(0.5), - alignSelf: 'flex-start', - }), - addLabelButton: css({ - flexGrow: 0, - alignSelf: 'flex-start', - }), - centerAlignRow: css({ - alignItems: 'baseline', - }), - equalSign: css({ - alignSelf: 'flex-start', - width: '28px', - justifyContent: 'center', - marginLeft: theme.spacing(0.5), - }), - labelInput: css({ - width: '175px', - marginBottom: `-${theme.spacing(1)}`, - '& + &': { - marginLeft: theme.spacing(1), - }, - }), - labelsContainer: css({ - marginBottom: theme.spacing(3), - }), - }; -}; - -export default LabelsField; 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 d4c9a8af1ee..2fbfdcc7f2d 100644 --- a/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import React from 'react'; +import React, { useState } from 'react'; import { useFormContext } from 'react-hook-form'; import { GrafanaTheme2 } from '@grafana/data'; @@ -9,10 +9,11 @@ import { Icon, RadioButtonGroup, Stack, Text, useStyles2 } from '@grafana/ui'; import { RuleFormType, RuleFormValues } from '../../types/rule-form'; import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; -import LabelsField from './LabelsField'; import { NeedHelpInfo } from './NeedHelpInfo'; import { RuleEditorSection } from './RuleEditorSection'; import { SimplifiedRouting } from './alert-rule-form/simplifiedRouting/SimplifiedRouting'; +import { LabelsEditorModal } from './labels/LabelsEditorModal'; +import { LabelsFieldInForm } from './labels/LabelsFieldInForm'; import { NotificationPreview } from './notificaton-preview/NotificationPreview'; type NotificationsStepProps = { @@ -25,16 +26,29 @@ enum RoutingOptions { } export const NotificationsStep = ({ alertUid }: NotificationsStepProps) => { - const { watch } = useFormContext(); + const { watch, getValues, setValue } = useFormContext(); const styles = useStyles2(getStyles); const [type] = watch(['type', 'labels', 'queries', 'condition', 'folder', 'name', 'manualRouting']); + const [showLabelsEditor, setShowLabelsEditor] = useState(false); const dataSourceName = watch('dataSourceName') ?? GRAFANA_RULES_SOURCE_NAME; const simplifiedRoutingToggleEnabled = config.featureToggles.alertingSimplifiedRouting ?? false; const shouldRenderpreview = type === RuleFormType.grafana; const shouldAllowSimplifiedRouting = type === RuleFormType.grafana && simplifiedRoutingToggleEnabled; + function onCloseLabelsEditor( + labelsToUpdate?: Array<{ + key: string; + value: string; + }> + ) { + if (labelsToUpdate) { + setValue('labels', labelsToUpdate); + } + setShowLabelsEditor(false); + } + return ( { } fullWidth > - + setShowLabelsEditor(true)} /> + {shouldAllowSimplifiedRouting && (
Notifications diff --git a/public/app/features/alerting/unified/components/rule-editor/labels/LabelsButtons.tsx b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsButtons.tsx new file mode 100644 index 00000000000..c7f9683bec6 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsButtons.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { UseFieldArrayRemove } from 'react-hook-form'; + +import { Button } from '@grafana/ui'; + +interface RemoveButtonProps { + remove: UseFieldArrayRemove; + index: number; +} +export function RemoveButton({ remove, index }: RemoveButtonProps) { + return ( + + ); +} diff --git a/public/app/features/alerting/unified/components/rule-editor/labels/LabelsEditorModal.tsx b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsEditorModal.tsx new file mode 100644 index 00000000000..3b6d5675fb5 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsEditorModal.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import { Modal } from '@grafana/ui'; + +import { LabelsSubForm } from './LabelsField'; + +export interface LabelsEditorModalProps { + isOpen: boolean; + initialLabels: Array<{ + key: string; + value: string; + }>; + onClose: ( + labelsToUodate?: Array<{ + key: string; + value: string; + }> + ) => void; + dataSourceName: string; +} +export function LabelsEditorModal({ isOpen, onClose, dataSourceName, initialLabels }: LabelsEditorModalProps) { + return ( + onClose()}> + + + ); +} diff --git a/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx new file mode 100644 index 00000000000..3f279087e5b --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx @@ -0,0 +1,176 @@ +import { render, screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { TestProvider } from 'test/helpers/TestProvider'; + +import { clearPluginSettingsCache } from 'app/features/plugins/pluginSettings'; + +import { mockAlertRuleApi, mockApi, setupMswServer } from '../../../mockApi'; +import { getGrafanaRule, labelsPluginMetaMock } from '../../../mocks'; +import { GRAFANA_RULES_SOURCE_NAME } from '../../../utils/datasource'; + +import LabelsField, { LabelsWithSuggestions } from './LabelsField'; + +const labels = [ + { key: 'key1', value: 'value1' }, + { key: 'key2', value: 'value2' }, +]; + +const FormProviderWrapper = ({ children }: React.PropsWithChildren<{}>) => { + const methods = useForm({ defaultValues: { labels } }); + return {children}; +}; +const SubFormProviderWrapper = ({ children }: React.PropsWithChildren<{}>) => { + const methods = useForm({ defaultValues: { labelsInSubform: labels } }); + return {children}; +}; + +function renderAlertLabels() { + render( + + + , + { wrapper: TestProvider } + ); +} + +function renderLabelsWithSuggestions() { + render( + + + , + { wrapper: TestProvider } + ); +} + +const grafanaRule = getGrafanaRule(undefined, { + uid: 'test-rule-uid', + title: 'cpu-usage', + namespace_uid: 'folderUID1', + data: [ + { + refId: 'A', + datasourceUid: 'uid1', + queryType: 'alerting', + relativeTimeRange: { from: 1000, to: 2000 }, + model: { + refId: 'A', + expression: 'vector(1)', + queryType: 'alerting', + datasource: { uid: 'uid1', type: 'prometheus' }, + }, + }, + ], +}); +const server = setupMswServer(); +describe('LabelsField with suggestions', () => { + afterEach(() => { + server.resetHandlers(); + clearPluginSettingsCache(); + }); + beforeEach(() => { + mockApi(server).plugins.getPluginSettings({ ...labelsPluginMetaMock, enabled: false }); + mockAlertRuleApi(server).rulerRules(GRAFANA_RULES_SOURCE_NAME, { + [grafanaRule.namespace.name]: [{ name: grafanaRule.group.name, interval: '1m', rules: [grafanaRule.rulerRule!] }], + }); + }); + + it('Should display two dropdowns with the existing labels', async () => { + renderLabelsWithSuggestions(); + + await waitFor(() => expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(2)); + + expect(screen.getByTestId('labelsInSubform-key-0').textContent).toBe('key1'); + expect(screen.getByTestId('labelsInSubform-key-1').textContent).toBe('key2'); + + expect(screen.getAllByTestId('alertlabel-value-picker')).toHaveLength(2); + + expect(screen.getByTestId('labelsInSubform-value-0').textContent).toBe('value1'); + expect(screen.getByTestId('labelsInSubform-value-1').textContent).toBe('value2'); + }); + + it('Should delete a key-value combination', async () => { + renderLabelsWithSuggestions(); + + await waitFor(() => expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(2)); + + expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(2); + expect(screen.getAllByTestId('alertlabel-value-picker')).toHaveLength(2); + + await userEvent.click(screen.getByTestId('delete-label-1')); + + expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(1); + expect(screen.getAllByTestId('alertlabel-value-picker')).toHaveLength(1); + }); + + it('Should add new key-value dropdowns', async () => { + renderLabelsWithSuggestions(); + + await waitFor(() => expect(screen.getByText('Add more')).toBeVisible()); + await userEvent.click(screen.getByText('Add more')); + + expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(3); + + expect(screen.getByTestId('labelsInSubform-key-0').textContent).toBe('key1'); + expect(screen.getByTestId('labelsInSubform-key-1').textContent).toBe('key2'); + expect(screen.getByTestId('labelsInSubform-key-2').textContent).toBe('Choose key'); + + expect(screen.getAllByTestId('alertlabel-value-picker')).toHaveLength(3); + + expect(screen.getByTestId('labelsInSubform-value-0').textContent).toBe('value1'); + expect(screen.getByTestId('labelsInSubform-value-1').textContent).toBe('value2'); + expect(screen.getByTestId('labelsInSubform-value-2').textContent).toBe('Choose value'); + }); + + it('Should be able to write new keys and values using the dropdowns', async () => { + renderLabelsWithSuggestions(); + + await waitFor(() => expect(screen.getByText('Add more')).toBeVisible()); + await userEvent.click(screen.getByText('Add more')); + + const LastKeyDropdown = within(screen.getByTestId('labelsInSubform-key-2')); + const LastValueDropdown = within(screen.getByTestId('labelsInSubform-value-2')); + + await userEvent.type(LastKeyDropdown.getByRole('combobox'), 'key3{enter}'); + await userEvent.type(LastValueDropdown.getByRole('combobox'), 'value3{enter}'); + + expect(screen.getByTestId('labelsInSubform-key-2').textContent).toBe('key3'); + expect(screen.getByTestId('labelsInSubform-value-2').textContent).toBe('value3'); + }); + it('Should be able to write new keys and values using the dropdowns, case sensitive', async () => { + renderLabelsWithSuggestions(); + + await waitFor(() => expect(screen.getAllByTestId('alertlabel-key-picker')).toHaveLength(2)); + expect(screen.getByTestId('labelsInSubform-key-0').textContent).toBe('key1'); + expect(screen.getByTestId('labelsInSubform-key-1').textContent).toBe('key2'); + expect(screen.getByTestId('labelsInSubform-value-0').textContent).toBe('value1'); + expect(screen.getByTestId('labelsInSubform-value-1').textContent).toBe('value2'); + + const LastKeyDropdown = within(screen.getByTestId('labelsInSubform-key-1')); + const LastValueDropdown = within(screen.getByTestId('labelsInSubform-value-1')); + + await userEvent.type(LastKeyDropdown.getByRole('combobox'), 'KEY2{enter}'); + expect(screen.getByTestId('labelsInSubform-key-0').textContent).toBe('key1'); + expect(screen.getByTestId('labelsInSubform-key-1').textContent).toBe('KEY2'); + + await userEvent.type(LastValueDropdown.getByRole('combobox'), 'VALUE2{enter}'); + expect(screen.getByTestId('labelsInSubform-value-0').textContent).toBe('value1'); + expect(screen.getByTestId('labelsInSubform-value-1').textContent).toBe('VALUE2'); + }); +}); + +describe('LabelsField without suggestions', () => { + it('Should display two inputs without label suggestions', async () => { + renderAlertLabels(); + + await waitFor(() => expect(screen.getAllByTestId('alertlabel-input-wrapper')).toHaveLength(2)); + expect(screen.queryAllByTestId('alertlabel-key-picker')).toHaveLength(0); + + expect(screen.getByTestId('label-key-0')).toHaveValue('key1'); + expect(screen.getByTestId('label-key-1')).toHaveValue('key2'); + + expect(screen.getByTestId('label-value-0')).toHaveValue('value1'); + expect(screen.getByTestId('label-value-1')).toHaveValue('value2'); + }); +}); diff --git a/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.tsx b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.tsx new file mode 100644 index 00000000000..685b1626c67 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.tsx @@ -0,0 +1,489 @@ +import { css, cx } from '@emotion/css'; +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { Controller, FormProvider, useFieldArray, useForm, useFormContext } from 'react-hook-form'; + +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { Button, Field, InlineLabel, Input, LoadingPlaceholder, Space, Stack, Text, useStyles2 } from '@grafana/ui'; +import { useDispatch } from 'app/types'; + +import { labelsApi } from '../../../api/labelsApi'; +import { usePluginBridge } from '../../../hooks/usePluginBridge'; +import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector'; +import { fetchRulerRulesIfNotFetchedYet } from '../../../state/actions'; +import { SupportedPlugin } from '../../../types/pluginBridges'; +import { RuleFormValues } from '../../../types/rule-form'; +import AlertLabelDropdown from '../../AlertLabelDropdown'; +import { AlertLabels } from '../../AlertLabels'; +import { NeedHelpInfo } from '../NeedHelpInfo'; + +import { AddButton, RemoveButton } from './LabelsButtons'; + +const useGetOpsLabelsKeys = (skip: boolean) => { + const { currentData, isLoading: isloadingLabels } = labelsApi.endpoints.getLabels.useQuery(undefined, { + skip, + }); + return { loading: isloadingLabels, labelsOpsKeys: currentData }; +}; +const useGetAlertRulesLabels = ( + dataSourceName: string +): { loading: boolean; labelsByKey: Record> } => { + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(fetchRulerRulesIfNotFetchedYet(dataSourceName)); + }, [dispatch, dataSourceName]); + + const rulerRuleRequests = useUnifiedAlertingSelector((state) => state.rulerRules); + const rulerRequest = rulerRuleRequests[dataSourceName]; + + const labelsByKeyResult = useMemo>>(() => { + const labelsByKey: Record> = {}; + + const rulerRulesConfig = rulerRequest?.result; + if (!rulerRulesConfig) { + return labelsByKey; + } + + const allRules = Object.values(rulerRulesConfig) + .flatMap((groups) => groups) + .flatMap((group) => group.rules); + + allRules.forEach((rule) => { + if (rule.labels) { + Object.entries(rule.labels).forEach(([key, value]) => { + if (!value) { + return; + } + + const labelEntry = labelsByKey[key]; + if (labelEntry) { + labelEntry.add(value); + } else { + labelsByKey[key] = new Set([value]); + } + }); + } + }); + + return labelsByKey; + }, [rulerRequest]); + + return { loading: rulerRequest?.loading, labelsByKey: labelsByKeyResult }; +}; + +function mapLabelsToOptions( + items: Iterable = [], + labelsInSubForm?: Array<{ key: string; value: string }> +): Array> { + const existingKeys = new Set(labelsInSubForm ? labelsInSubForm.map((label) => label.key) : []); + return Array.from(items, (item) => ({ label: item, value: item, isDisabled: existingKeys.has(item) })); +} + +export interface LabelsInRuleProps { + labels: Array<{ key: string; value: string }>; +} + +export const LabelsInRule = ({ labels }: LabelsInRuleProps) => { + const labelsObj: Record = labels.reduce((acc: Record, label) => { + if (label.key) { + acc[label.key] = label.value; + } + return acc; + }, {}); + + return ; +}; + +export type LabelsSubformValues = { + labelsInSubform: Array<{ key: string; value: string }>; +}; + +export interface LabelsSubFormProps { + dataSourceName: string; + initialLabels: Array<{ key: string; value: string }>; + onClose: ( + labelsToUodate?: Array<{ + key: string; + value: string; + }> + ) => void; +} + +export function LabelsSubForm({ dataSourceName, onClose, initialLabels }: LabelsSubFormProps) { + const styles = useStyles2(getStyles); + + const onSave = (labels: LabelsSubformValues) => { + onClose(labels.labelsInSubform); + }; + const onCancel = () => { + onClose(); + }; + // default values for the subform are the initial labels + const defaultValues: LabelsSubformValues = useMemo(() => { + return { labelsInSubform: initialLabels }; + }, [initialLabels]); + + const formAPI = useForm({ defaultValues }); + return ( + +
+ + Add labels to your rule for searching, silencing, or routing to a notification policy. + + + + + +
+ + +
+
+
+ +
+ ); +} +export function useCombinedLabels( + dataSourceName: string, + labelsPluginInstalled: boolean, + loadingLabelsPlugin: boolean, + labelsInSubform: Array<{ key: string; value: string }>, + selectedKey: string +) { + // ------- Get labels keys and their values from existing alerts + const { loading, labelsByKey: labelsByKeyFromExisingAlerts } = useGetAlertRulesLabels(dataSourceName); + // ------- Get only the keys from the ops labels, as we will fetch the values for the keys once the key is selected. + const { loading: isLoadingLabels, labelsOpsKeys = [] } = useGetOpsLabelsKeys( + !labelsPluginInstalled || loadingLabelsPlugin + ); + //------ Convert the labelsOpsKeys to the same format as the labelsByKeyFromExisingAlerts + const labelsByKeyOps = useMemo(() => { + return labelsOpsKeys.reduce((acc: Record>, label) => { + acc[label.name] = new Set(); + return acc; + }, {}); + }, [labelsOpsKeys]); + + //------- Convert the keys from the ops labels to options for the dropdown + const keysFromGopsLabels = useMemo(() => { + return mapLabelsToOptions(Object.keys(labelsByKeyOps), labelsInSubform); + }, [labelsByKeyOps, labelsInSubform]); + + //------- Convert the keys from the existing alerts to options for the dropdown + const keysFromExistingAlerts = useMemo(() => { + return mapLabelsToOptions(Object.keys(labelsByKeyFromExisingAlerts), labelsInSubform); + }, [labelsByKeyFromExisingAlerts, labelsInSubform]); + + // create two groups of labels, one for ops and one for custom + const groupedOptions = [ + { + label: 'From alerts', + options: keysFromExistingAlerts, + expanded: true, + }, + { + label: 'From system', + options: keysFromGopsLabels, + expanded: true, + }, + ]; + + const selectedKeyIsFromAlerts = + labelsByKeyFromExisingAlerts[selectedKey] !== undefined && labelsByKeyFromExisingAlerts[selectedKey]?.size > 0; + const selectedKeyIsFromOps = labelsByKeyOps[selectedKey] !== undefined && labelsByKeyOps[selectedKey]?.size > 0; + const selectedKeyDoesNotExist = !selectedKeyIsFromAlerts && !selectedKeyIsFromOps; + + const valuesAlreadyFetched = !selectedKeyIsFromAlerts && labelsByKeyOps[selectedKey]?.size > 0; + + // Only fetch the values for the selected key if it is from ops and the values are not already fetched (the selected key is not in the labelsByKeyOps object) + const { + currentData: valuesData, + isLoading: isLoadingValues = false, + error, + } = labelsApi.endpoints.getLabelValues.useQuery( + { key: selectedKey }, + { + skip: + !labelsPluginInstalled || + !selectedKey || + selectedKeyIsFromAlerts || + valuesAlreadyFetched || + selectedKeyDoesNotExist, + } + ); + + // these are the values for the selected key in case it is from ops + const valuesFromSelectedGopsKey = useMemo(() => { + // if it is from alerts, we need to fetch the values from the existing alerts + if (selectedKeyIsFromAlerts) { + return []; + } + // in case of a label from ops, we need to fetch the values from the plugin + // fetch values from ops only if there is no value for the key + const valuesForSelectedKey = labelsByKeyOps[selectedKey]; + const valuesAlreadyFetched = valuesForSelectedKey?.size > 0; + if (valuesAlreadyFetched) { + return mapLabelsToOptions(valuesForSelectedKey); + } + if (!isLoadingValues && valuesData?.values?.length && !error) { + const values = valuesData?.values.map((value) => value.name); + labelsByKeyOps[selectedKey] = new Set(values); + return mapLabelsToOptions(values); + } + return []; + }, [selectedKeyIsFromAlerts, labelsByKeyOps, selectedKey, isLoadingValues, valuesData, error]); + + const getValuesForLabel = useCallback( + (key: string) => { + // values from existing alerts will take precedence over values from ops + if (selectedKeyIsFromAlerts || !labelsPluginInstalled) { + return mapLabelsToOptions(labelsByKeyFromExisingAlerts[key]); + } + return valuesFromSelectedGopsKey; + }, + [labelsByKeyFromExisingAlerts, labelsPluginInstalled, valuesFromSelectedGopsKey, selectedKeyIsFromAlerts] + ); + + return { + loading: loading || isLoadingLabels, + keysFromExistingAlerts, + groupedOptions, + getValuesForLabel, + }; +} +/* + We will suggest labels from two sources: existing alerts and ops labels. + We only will suggest labels from ops if the grafana-labels-app plugin is installed + This component is only used by the alert rule form. + */ +export interface LabelsWithSuggestionsProps { + dataSourceName: string; +} +export function LabelsWithSuggestions({ dataSourceName }: LabelsWithSuggestionsProps) { + const styles = useStyles2(getStyles); + const { + control, + watch, + formState: { errors }, + } = useFormContext(); + + const labelsInSubform = watch('labelsInSubform'); + const { fields, remove, append } = useFieldArray({ control, name: 'labelsInSubform' }); + const appendLabel = useCallback(() => { + append({ key: '', value: '' }); + }, [append]); + + // check if the labels plugin is installed + const { installed: labelsPluginInstalled = false, loading: loadingLabelsPlugin } = usePluginBridge( + SupportedPlugin.Labels + ); + const [selectedKey, setSelectedKey] = useState(''); + + const { loading, keysFromExistingAlerts, groupedOptions, getValuesForLabel } = useCombinedLabels( + dataSourceName, + labelsPluginInstalled, + loadingLabelsPlugin, + labelsInSubform, + selectedKey + ); + + const values = useMemo(() => { + return getValuesForLabel(selectedKey); + }, [selectedKey, getValuesForLabel]); + + const isLoading = loading || loadingLabelsPlugin; + + return ( + <> + {isLoading && } + {!isLoading && ( + + {fields.map((field, index) => { + return ( +
+ + { + return ( + { + onChange(newValue.value); + setSelectedKey(newValue.value); + }} + type="key" + /> + ); + }} + /> + + = + + { + return ( + { + onChange(newValue.value); + }} + onOpenMenu={() => { + setSelectedKey(labelsInSubform[index].key); + }} + type="value" + /> + ); + }} + /> + + + +
+ ); + })} + +
+ )} + + ); +} + +export const LabelsWithoutSuggestions: FC = () => { + const styles = useStyles2(getStyles); + const { + register, + control, + watch, + formState: { errors }, + } = useFormContext(); + + const labels = watch('labels'); + const { fields, remove, append } = useFieldArray({ control, name: 'labels' }); + const appendLabel = useCallback(() => { + append({ key: '', value: '' }); + }, [append]); + + return ( + <> + {fields.map((field, index) => { + return ( +
+
+ + + + = + + + + +
+
+ ); + })} + + + ); +}; + +function LabelsField() { + return ( +
+ + Labels + + + Add labels to your rule for searching, silencing, or routing to a notification policy. + + + + + +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + flexColumn: css({ + display: 'flex', + flexDirection: 'column', + }), + flexRow: css({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + }), + centerAlignRow: css({ + alignItems: 'center', + gap: theme.spacing(0.5), + }), + equalSign: css({ + alignSelf: 'flex-start', + width: '28px', + justifyContent: 'center', + margin: 0, + }), + labelInput: css({ + width: '175px', + margin: 0, + }), + confirmButton: css({ + display: 'flex', + flexDirection: 'row', + gap: theme.spacing(1), + marginLeft: 'auto', + }), + }; +}; + +export default LabelsField; diff --git a/public/app/features/alerting/unified/components/rule-editor/labels/LabelsFieldInForm.tsx b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsFieldInForm.tsx new file mode 100644 index 00000000000..14d7319b088 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsFieldInForm.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { useFormContext } from 'react-hook-form'; + +import { Button, Stack, Text } from '@grafana/ui'; + +import { RuleFormValues } from '../../../types/rule-form'; +import { NeedHelpInfo } from '../NeedHelpInfo'; + +import { LabelsInRule } from './LabelsField'; + +interface LabelsFieldInFormProps { + onEditClick: () => void; +} +export function LabelsFieldInForm({ onEditClick }: LabelsFieldInFormProps) { + const { watch } = useFormContext(); + const labels = watch('labels'); + const hasLabels = Object.keys(labels).length > 0 && labels.some((label) => label.key || label.value); + + return ( + + + Labels + + + Add labels to your rule for searching, silencing, or routing to a notification policy. + + + + + + + {hasLabels ? ( + + ) : ( + + No labels selected + + + )} + + + ); +} diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 458d97df016..7b97e7aeb8c 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -747,3 +747,23 @@ export const onCallPluginMetaMock: PluginMeta = { screenshots: [], }, }; + +export const labelsPluginMetaMock: PluginMeta = { + name: 'Grafana IRM Labels', + id: 'grafana-labels-app', + type: PluginType.app, + module: 'plugins/grafana-labels-app/module', + baseUrl: 'public/plugins/grafana-labels-app', + info: { + author: { name: 'Grafana Labs' }, + description: '', + updated: '', + version: '', + links: [], + logos: { + small: '', + large: '', + }, + screenshots: [], + }, +}; diff --git a/public/app/features/alerting/unified/types/pluginBridges.ts b/public/app/features/alerting/unified/types/pluginBridges.ts index bb761008393..fbe809743b6 100644 --- a/public/app/features/alerting/unified/types/pluginBridges.ts +++ b/public/app/features/alerting/unified/types/pluginBridges.ts @@ -2,4 +2,5 @@ export enum SupportedPlugin { Incident = 'grafana-incident-app', OnCall = 'grafana-oncall-app', MachineLearning = 'grafana-ml-app', + Labels = 'grafana-labels-app', } From a6ad2380bf6d0634abd27f4646869bb6973c4354 Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Tue, 23 Apr 2024 14:50:26 +0200 Subject: [PATCH 051/222] Alerting: Refactor api_prometheus.go request handlers. (#86639) This splits the request handlers into two functions, one which is the actual handler and one which is independent from the Grafana `ReqContext` object. This is to make it easier to reuse the implementation in other code. Part of the refactoring changes the functions which get query parameters from the request to operate on a `url.Values` instead of the request object. The change also makes the code consistently use `req.Form` instead of a combination of `req.URL.Query()` and `req.Form`, though I have left `api_ruler` as-is to avoid this PR growing too large. --- pkg/services/ngalert/api/api_prometheus.go | 197 +++++++++++++-------- pkg/services/ngalert/api/api_ruler.go | 2 +- 2 files changed, 126 insertions(+), 73 deletions(-) diff --git a/pkg/services/ngalert/api/api_prometheus.go b/pkg/services/ngalert/api/api_prometheus.go index dae93551bea..d2601fa7e96 100644 --- a/pkg/services/ngalert/api/api_prometheus.go +++ b/pkg/services/ngalert/api/api_prometheus.go @@ -1,10 +1,10 @@ package api import ( + "context" "encoding/json" "errors" "fmt" - "net/http" "net/url" "sort" "strconv" @@ -17,7 +17,6 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" - "github.com/grafana/grafana/pkg/services/folder" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/eval" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" @@ -61,6 +60,20 @@ func (srv PrometheusSrv) RouteGetAlertStatuses(c *contextmodel.ReqContext) respo // As we are using req.Form directly, this triggers a call to ParseForm() if needed. c.Query("") + resp := PrepareAlertStatuses(srv.manager, AlertStatusesOptions{ + OrgID: c.SignedInUser.GetOrgID(), + Query: c.Req.Form, + }) + + return response.JSON(resp.HTTPStatusCode(), resp) +} + +type AlertStatusesOptions struct { + OrgID int64 + Query url.Values +} + +func PrepareAlertStatuses(manager state.AlertInstanceManager, opts AlertStatusesOptions) apimodels.AlertResponse { alertResponse := apimodels.AlertResponse{ DiscoveryBase: apimodels.DiscoveryBase{ Status: "success", @@ -71,11 +84,11 @@ func (srv PrometheusSrv) RouteGetAlertStatuses(c *contextmodel.ReqContext) respo } var labelOptions []ngmodels.LabelOption - if !getBoolWithDefault(c.Req.Form, queryIncludeInternalLabels, false) { + if !getBoolWithDefault(opts.Query, queryIncludeInternalLabels, false) { labelOptions = append(labelOptions, ngmodels.WithoutInternalLabels()) } - for _, alertState := range srv.manager.GetAll(c.SignedInUser.GetOrgID()) { + for _, alertState := range manager.GetAll(opts.OrgID) { startsAt := alertState.StartsAt valString := "" @@ -95,7 +108,7 @@ func (srv PrometheusSrv) RouteGetAlertStatuses(c *contextmodel.ReqContext) respo }) } - return response.JSON(alertResponse.HTTPStatusCode(), alertResponse) + return alertResponse } func formatValues(alertState *state.State) string { @@ -126,16 +139,16 @@ func formatValues(alertState *state.State) string { return fv } -func getPanelIDFromRequest(r *http.Request) (int64, error) { - if s := strings.TrimSpace(r.URL.Query().Get("panel_id")); s != "" { +func getPanelIDFromQuery(v url.Values) (int64, error) { + if s := strings.TrimSpace(v.Get("panel_id")); s != "" { return strconv.ParseInt(s, 10, 64) } return 0, nil } -func getMatchersFromRequest(r *http.Request) (labels.Matchers, error) { +func getMatchersFromQuery(v url.Values) (labels.Matchers, error) { var matchers labels.Matchers - for _, s := range r.URL.Query()["matcher"] { + for _, s := range v["matcher"] { var m labels.Matcher if err := json.Unmarshal([]byte(s), &m); err != nil { return nil, err @@ -148,9 +161,9 @@ func getMatchersFromRequest(r *http.Request) (labels.Matchers, error) { return matchers, nil } -func getStatesFromRequest(r *http.Request) ([]eval.State, error) { +func getStatesFromQuery(v url.Values) ([]eval.State, error) { var states []eval.State - for _, s := range r.URL.Query()["state"] { + for _, s := range v["state"] { s = strings.ToLower(s) switch s { case "normal", "inactive": @@ -171,6 +184,18 @@ func getStatesFromRequest(r *http.Request) ([]eval.State, error) { return states, nil } +type RuleGroupStatusesOptions struct { + Ctx context.Context + OrgID int64 + Query url.Values + Namespaces map[string]string + AuthorizeRuleGroup func(rules []*ngmodels.AlertRule) (bool, error) +} + +type ListAlertRulesStore interface { + ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) (ngmodels.RulesGroup, error) +} + func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) response.Response { // As we are using req.Form directly, this triggers a call to ParseForm() if needed. c.Query("") @@ -184,48 +209,6 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon }, } - dashboardUID := c.Query("dashboard_uid") - panelID, err := getPanelIDFromRequest(c.Req) - if err != nil { - ruleResponse.DiscoveryBase.Status = "error" - ruleResponse.DiscoveryBase.Error = fmt.Sprintf("invalid panel_id: %s", err.Error()) - ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData - return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) - } - if dashboardUID == "" && panelID != 0 { - ruleResponse.DiscoveryBase.Status = "error" - ruleResponse.DiscoveryBase.Error = "panel_id must be set with dashboard_uid" - ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData - return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) - } - - limitGroups := getInt64WithDefault(c.Req.Form, "limit", -1) - limitRulesPerGroup := getInt64WithDefault(c.Req.Form, "limit_rules", -1) - limitAlertsPerRule := getInt64WithDefault(c.Req.Form, "limit_alerts", -1) - matchers, err := getMatchersFromRequest(c.Req) - if err != nil { - ruleResponse.DiscoveryBase.Status = "error" - ruleResponse.DiscoveryBase.Error = err.Error() - ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData - return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) - } - withStates, err := getStatesFromRequest(c.Req) - if err != nil { - ruleResponse.DiscoveryBase.Status = "error" - ruleResponse.DiscoveryBase.Error = err.Error() - ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData - return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) - } - withStatesFast := make(map[eval.State]struct{}) - for _, state := range withStates { - withStatesFast[state] = struct{}{} - } - - var labelOptions []ngmodels.LabelOption - if !getBoolWithDefault(c.Req.Form, queryIncludeInternalLabels, false) { - labelOptions = append(labelOptions, ngmodels.WithoutInternalLabels()) - } - namespaceMap, err := srv.store.GetUserVisibleNamespaces(c.Req.Context(), c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { ruleResponse.DiscoveryBase.Status = "error" @@ -234,28 +217,98 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) } - if len(namespaceMap) == 0 { - srv.log.Debug("User does not have access to any namespaces") - return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) + namespaces := map[string]string{} + for namespaceUID, folder := range namespaceMap { + namespaces[namespaceUID] = folder.Fullpath } - namespaceUIDs := make([]string, len(namespaceMap)) - for k := range namespaceMap { + ruleResponse = PrepareRuleGroupStatuses(srv.log, srv.manager, srv.store, RuleGroupStatusesOptions{ + Ctx: c.Req.Context(), + OrgID: c.OrgID, + Query: c.Req.Form, + Namespaces: namespaces, + AuthorizeRuleGroup: func(rules []*ngmodels.AlertRule) (bool, error) { + return srv.authz.HasAccessToRuleGroup(c.Req.Context(), c.SignedInUser, rules) + }, + }) + + return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) +} + +func PrepareRuleGroupStatuses(log log.Logger, manager state.AlertInstanceManager, store ListAlertRulesStore, opts RuleGroupStatusesOptions) apimodels.RuleResponse { + ruleResponse := apimodels.RuleResponse{ + DiscoveryBase: apimodels.DiscoveryBase{ + Status: "success", + }, + Data: apimodels.RuleDiscovery{ + RuleGroups: []apimodels.RuleGroup{}, + }, + } + + dashboardUID := opts.Query.Get("dashboard_uid") + panelID, err := getPanelIDFromQuery(opts.Query) + if err != nil { + ruleResponse.DiscoveryBase.Status = "error" + ruleResponse.DiscoveryBase.Error = fmt.Sprintf("invalid panel_id: %s", err.Error()) + ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData + return ruleResponse + } + if dashboardUID == "" && panelID != 0 { + ruleResponse.DiscoveryBase.Status = "error" + ruleResponse.DiscoveryBase.Error = "panel_id must be set with dashboard_uid" + ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData + return ruleResponse + } + + limitGroups := getInt64WithDefault(opts.Query, "limit", -1) + limitRulesPerGroup := getInt64WithDefault(opts.Query, "limit_rules", -1) + limitAlertsPerRule := getInt64WithDefault(opts.Query, "limit_alerts", -1) + matchers, err := getMatchersFromQuery(opts.Query) + if err != nil { + ruleResponse.DiscoveryBase.Status = "error" + ruleResponse.DiscoveryBase.Error = err.Error() + ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData + return ruleResponse + } + withStates, err := getStatesFromQuery(opts.Query) + if err != nil { + ruleResponse.DiscoveryBase.Status = "error" + ruleResponse.DiscoveryBase.Error = err.Error() + ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrBadData + return ruleResponse + } + withStatesFast := make(map[eval.State]struct{}) + for _, state := range withStates { + withStatesFast[state] = struct{}{} + } + + var labelOptions []ngmodels.LabelOption + if !getBoolWithDefault(opts.Query, queryIncludeInternalLabels, false) { + labelOptions = append(labelOptions, ngmodels.WithoutInternalLabels()) + } + + if len(opts.Namespaces) == 0 { + log.Debug("User does not have access to any namespaces") + return ruleResponse + } + + namespaceUIDs := make([]string, len(opts.Namespaces)) + for k := range opts.Namespaces { namespaceUIDs = append(namespaceUIDs, k) } alertRuleQuery := ngmodels.ListAlertRulesQuery{ - OrgID: c.SignedInUser.GetOrgID(), + OrgID: opts.OrgID, NamespaceUIDs: namespaceUIDs, DashboardUID: dashboardUID, PanelID: panelID, } - ruleList, err := srv.store.ListAlertRules(c.Req.Context(), &alertRuleQuery) + ruleList, err := store.ListAlertRules(opts.Ctx, &alertRuleQuery) if err != nil { ruleResponse.DiscoveryBase.Status = "error" ruleResponse.DiscoveryBase.Error = fmt.Sprintf("failure getting rules: %s", err.Error()) ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrServer - return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) + return ruleResponse } // Group rules together by Namespace and Rule Group. Rules are also grouped by Org ID, @@ -275,22 +328,22 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon rulesTotals := make(map[string]int64, len(groupedRules)) for groupKey, rules := range groupedRules { - folder := namespaceMap[groupKey.NamespaceUID] - if folder == nil { - srv.log.Warn("Query returned rules that belong to folder the user does not have access to. All rules that belong to that namespace will not be added to the response", "folder_uid", groupKey.NamespaceUID) + folder, ok := opts.Namespaces[groupKey.NamespaceUID] + if !ok { + log.Warn("Query returned rules that belong to folder the user does not have access to. All rules that belong to that namespace will not be added to the response", "folder_uid", groupKey.NamespaceUID) continue } - ok, err := srv.authz.HasAccessToRuleGroup(c.Req.Context(), c.SignedInUser, rules) + ok, err := opts.AuthorizeRuleGroup(rules) if err != nil { ruleResponse.DiscoveryBase.Status = "error" ruleResponse.DiscoveryBase.Error = fmt.Sprintf("cannot authorize access to rule group: %s", err.Error()) ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrServer - return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) + return ruleResponse } if !ok { continue } - ruleGroup, totals := srv.toRuleGroup(groupKey, folder, rules, limitAlertsPerRule, withStatesFast, matchers, labelOptions) + ruleGroup, totals := toRuleGroup(log, manager, groupKey, folder, rules, limitAlertsPerRule, withStatesFast, matchers, labelOptions) ruleGroup.Totals = totals for k, v := range totals { rulesTotals[k] += v @@ -335,7 +388,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon ruleResponse.Data.RuleGroups = ruleResponse.Data.RuleGroups[0:limitGroups] } - return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) + return ruleResponse } // This is the same as matchers.Matches but avoids the need to create a LabelSet @@ -348,11 +401,11 @@ func matchersMatch(matchers []*labels.Matcher, labels map[string]string) bool { return true } -func (srv PrometheusSrv) toRuleGroup(groupKey ngmodels.AlertRuleGroupKey, folder *folder.Folder, rules []*ngmodels.AlertRule, limitAlerts int64, withStates map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption) (*apimodels.RuleGroup, map[string]int64) { +func toRuleGroup(log log.Logger, manager state.AlertInstanceManager, groupKey ngmodels.AlertRuleGroupKey, folderFullPath string, rules []*ngmodels.AlertRule, limitAlerts int64, withStates map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption) (*apimodels.RuleGroup, map[string]int64) { newGroup := &apimodels.RuleGroup{ Name: groupKey.RuleGroup, // file is what Prometheus uses for provisioning, we replace it with namespace which is the folder in Grafana. - File: folder.Fullpath, + File: folderFullPath, } rulesTotals := make(map[string]int64, len(rules)) @@ -362,7 +415,7 @@ func (srv PrometheusSrv) toRuleGroup(groupKey ngmodels.AlertRuleGroupKey, folder alertingRule := apimodels.AlertingRule{ State: "inactive", Name: rule.Title, - Query: ruleToQuery(srv.log, rule), + Query: ruleToQuery(log, rule), Duration: rule.For.Seconds(), Annotations: rule.Annotations, } @@ -375,7 +428,7 @@ func (srv PrometheusSrv) toRuleGroup(groupKey ngmodels.AlertRuleGroupKey, folder LastEvaluation: time.Time{}, } - states := srv.manager.GetStatesForRuleUID(rule.OrgID, rule.UID) + states := manager.GetStatesForRuleUID(rule.OrgID, rule.UID) totals := make(map[string]int64) totalsFiltered := make(map[string]int64) for _, alertState := range states { diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 8a77b72a9cf..53f1e1464cb 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -234,7 +234,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res } dashboardUID := c.Query("dashboard_uid") - panelID, err := getPanelIDFromRequest(c.Req) + panelID, err := getPanelIDFromQuery(c.Req.URL.Query()) if err != nil { return ErrResp(http.StatusBadRequest, err, "invalid panel_id") } From 7067e37971042d6d501b88a387b0aad82401b3c0 Mon Sep 17 00:00:00 2001 From: antonio <45235678+tonypowa@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:07:29 +0200 Subject: [PATCH 052/222] alerting:labels-annotations elaboration (#86663) * alerting:labels-annotations elaboration * applied suggestions * applied suggestions 2 * modified intro, and labels sections * Update docs/sources/alerting/fundamentals/alert-rules/annotation-label.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/alerting/fundamentals/alert-rules/annotation-label.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/alerting/fundamentals/alert-rules/annotation-label.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/alerting/fundamentals/alert-rules/annotation-label.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/alerting/fundamentals/alert-rules/annotation-label.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/alerting/fundamentals/alert-rules/annotation-label.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/alerting/fundamentals/alert-rules/annotation-label.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/alerting/fundamentals/alert-rules/annotation-label.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * pretty --------- Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> --- .../alert-rules/annotation-label.md | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/sources/alerting/fundamentals/alert-rules/annotation-label.md b/docs/sources/alerting/fundamentals/alert-rules/annotation-label.md index 5317dd7b3b1..26b989efa61 100644 --- a/docs/sources/alerting/fundamentals/alert-rules/annotation-label.md +++ b/docs/sources/alerting/fundamentals/alert-rules/annotation-label.md @@ -26,11 +26,15 @@ weight: 105 Labels and annotations contain information about an alert. Labels are used to differentiate an alert from all other alerts, while annotations are used to add additional information to an existing alert. +When creating alert rules, you can also template labels and annotations to optimize and customize your alerts. + ## Labels -Labels contain information that identifies an alert. An example of a label might be `server=server1` or `team=backend`. Each alert can have more than one label, and the complete set of labels for an alert is called its label set. It is this label set that identifies the alert. +**Labels** are unique identifiers of an alert. You can use them for searching, silencing, and routing notifications. -For example, an alert might have the label set `{alertname="High CPU usage",server="server1"}` while another alert might have the label set `{alertname="High CPU usage",server="server2"}`. These are two separate alerts because although their `alertname` labels are the same, their `server` labels are different. +Examples of labels are `server=server1` or `team=backend`. Each alert rule can have more than one label and the complete set of labels for an alert rule is called its label set. It is this label set that identifies the alert. + +For example, an alert rule might have the label set `{alertname="High CPU usage",server="server1"}` while another alert rule might have the label set `{alertname="High CPU usage",server="server2"}`. These are two separate alert rules because although their `alertname` labels are the same, their `server` labels are different. Labels are a fundamental component of alerting: @@ -94,15 +98,16 @@ Here is an example that shows how to exclude the label `Team`. You can choose be An alert's label set can contain three types of labels: -- Labels from the datasource, -- Custom labels specified in the alert rule, -- A series of reserved labels, such as `alertname` or `grafana_folder`. +- Data source query labels. For example, if you are monitoring temperature readings and each time series for these readings has a sensor_id, and a location label. These labels are used to provide additional context or dimensions to the metric data, helping to differentiate between different time series. -### Custom Labels +- Labels that are automatically added by Grafana (i.e. alertname and grafana_folder). These are Grafana reserved labels. -Custom labels are additional labels configured manually in the alert rule. +- Labels that you define yourself to help filter data in your alert rules. + You can also template labels. For example in your alert rule, you could add a label that uses templating to create more dynamic and customizable alerting. E.g. `environment` `=` `{{ your text/template }}`. -Ensure the label set for an alert does not have two or more labels with the same name. If a custom label has the same name as a label from the datasource then it will replace that label. However, should a custom label have the same name as a reserved label then the custom label will be omitted from the alert. +{{}} +Ensure the label set for an alert does not have two or more labels with the same name. If a label has the same name as a label from the data source then it will replace that label. However, should a label have the same name as a reserved label then the label will be omitted from the alert. +{{}} {{< collapse title="Key format" >}} @@ -122,7 +127,7 @@ If multiple label keys are sanitized to the same value, the duplicates will have ### Reserved labels -Reserved labels can be used in the same way as manually configured custom labels. The current list of available reserved labels are: +Reserved labels can be used in the same way as manually configured labels. The current list of available reserved labels are: | Label | Description | | -------------- | ----------------------------------------- | @@ -135,7 +140,7 @@ Labels prefixed with `grafana_` are reserved by Grafana for special use. To stop Both labels and annotations have the same structure: a set of named values; however their intended uses are different. The purpose of annotations is to add additional information to existing alerts. -There are a number of suggested annotations in Grafana such as `description`, `summary`, `runbook_url`, `dashboardUId` and `panelId`. Like custom labels, annotations must have a name, and their value can contain a combination of text and template code that is evaluated when an alert is fired. +There are a number of suggested annotations in Grafana such as `description`, `summary`, `runbook_url`, `dashboardUId` and `panelId`. Like labels, annotations must have a name, and their value can contain a combination of text and template code that is evaluated when an alert is fired. {{% docs/reference %}} [variables-label-annotation]: "/docs/grafana/ -> /docs/grafana//alerting/alerting-rules/templating-labels-annotations" From b28e6bc5d8547eda9754dac94a3faa4bb340e360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 23 Apr 2024 15:25:16 +0200 Subject: [PATCH 053/222] DashboardScene: Fixes issue with editing panels that uses instanceState (#86687) * DashboardScene: Fixes issue with editing panels that uses instanceState * Minor tweak * Update * Update --- package.json | 2 +- .../panel-edit/PanelOptions.tsx | 6 +++--- .../scene/setDashboardPanelContext.ts | 5 ----- yarn.lock | 19 ++++++++++++++++--- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 7af5c99baa9..090b3230876 100644 --- a/package.json +++ b/package.json @@ -252,7 +252,7 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "^4.10.0", + "@grafana/scenes": "^4.12.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx b/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx index a7f68110266..7da739a385a 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx @@ -23,7 +23,7 @@ interface Props { export const PanelOptions = React.memo(({ vizManager, searchQuery, listMode, data }) => { const { panel, sourcePanel, repeat } = vizManager.useState(); const parent = sourcePanel.resolve().parent; - const { options, fieldConfig } = panel.useState(); + const { options, fieldConfig, _pluginInstanceState } = panel.useState(); // eslint-disable-next-line react-hooks/exhaustive-deps const panelFrameOptions = useMemo( @@ -42,10 +42,10 @@ export const PanelOptions = React.memo(({ vizManager, searchQuery, listMo data, plugin: plugin, eventBus: panel.getPanelContext().eventBus, - instanceState: panel.getPanelContext().instanceState!, + instanceState: _pluginInstanceState, }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [panel, options, fieldConfig]); + }, [panel, options, fieldConfig, _pluginInstanceState]); const libraryPanelOptions = useMemo(() => { if (parent instanceof LibraryVizPanel) { diff --git a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts index 579f74d2a55..b94b3f7e2aa 100644 --- a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts +++ b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts @@ -121,11 +121,6 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte //return onUpdatePanelSnapshotData(this.props.panel, frames); return Promise.resolve(true); }; - - // Backward compatibility with id - context.instanceState = { - legacyPanelId: getPanelIdForVizPanel(vizPanel), - }; } function getBuiltInAnnotationsLayer(scene: DashboardScene): dataLayers.AnnotationsDataLayer | undefined { diff --git a/yarn.lock b/yarn.lock index dfb3a877044..b94dd695064 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4180,7 +4180,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:^4.10.0": +"@grafana/scenes@npm:^4.12.0": version: 4.12.0 resolution: "@grafana/scenes@npm:4.12.0" dependencies: @@ -18599,7 +18599,7 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:^4.10.0" + "@grafana/scenes": "npm:^4.12.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^1.3.0-rc1" @@ -26208,7 +26208,20 @@ __metadata: languageName: node linkType: hard -"rc-util@npm:^5.15.0, rc-util@npm:^5.16.1, rc-util@npm:^5.21.0, rc-util@npm:^5.24.4, rc-util@npm:^5.27.0, rc-util@npm:^5.36.0, rc-util@npm:^5.37.0, rc-util@npm:^5.38.0, rc-util@npm:^5.38.1": +"rc-util@npm:^5.15.0, rc-util@npm:^5.16.1, rc-util@npm:^5.21.0, rc-util@npm:^5.24.4, rc-util@npm:^5.27.0, rc-util@npm:^5.37.0, rc-util@npm:^5.38.0, rc-util@npm:^5.38.1": + version: 5.38.2 + resolution: "rc-util@npm:5.38.2" + dependencies: + "@babel/runtime": "npm:^7.18.3" + react-is: "npm:^18.2.0" + peerDependencies: + react: ">=16.9.0" + react-dom: ">=16.9.0" + checksum: 10/f8d8b21d0ed09de6fcf6c24dc19bf82f8f1fd089a625d35fd399626280ed33e73b9a703aa78f1a09ccd40b83f50e73bbc993adf892355a01857d3a1bb83e0958 + languageName: node + linkType: hard + +"rc-util@npm:^5.36.0": version: 5.39.1 resolution: "rc-util@npm:5.39.1" dependencies: From a6be12c0378693c6431d348709f398b97a301546 Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 23 Apr 2024 15:45:35 +0200 Subject: [PATCH 054/222] Alerting: Implement SaveAndApplyConfig in the forked Alertmanager (remote primary) (#84659) * Alerting: Implement SaveAndApplyConfiguration in the forked Alertmanager struct * call SaveAndApplyConfig on the remote first, log errors for the internal * add comments explaining why we ignore errors in the internal AM * restore go.work.sum --- .../remote/forked_alertmanager_test.go | 20 +++++++++++++++++++ .../remote_primary_forked_alertmanager.go | 13 ++++++++++++ 2 files changed, 33 insertions(+) diff --git a/pkg/services/ngalert/remote/forked_alertmanager_test.go b/pkg/services/ngalert/remote/forked_alertmanager_test.go index 278d38efaa0..80d40da3617 100644 --- a/pkg/services/ngalert/remote/forked_alertmanager_test.go +++ b/pkg/services/ngalert/remote/forked_alertmanager_test.go @@ -406,6 +406,26 @@ func TestForkedAlertmanager_ModeRemotePrimary(t *testing.T) { require.NoError(tt, forked.SaveAndApplyDefaultConfig(ctx)) }) + t.Run("SaveAndApplyConfig", func(tt *testing.T) { + // SaveAndApplyConfig should first be called on the remote Alertmanager + // and then on the internal one. + internal, remote, forked := genTestAlertmanagers(tt, modeRemotePrimary) + remoteCall := remote.EXPECT().SaveAndApplyConfig(ctx, mock.Anything).Return(nil).Once() + internal.EXPECT().SaveAndApplyConfig(ctx, mock.Anything).Return(nil).Once().NotBefore(remoteCall) + require.NoError(tt, forked.SaveAndApplyConfig(ctx, &apimodels.PostableUserConfig{})) + + // If there's an error in the remote Alertmanager, it should be returned. + _, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().SaveAndApplyConfig(ctx, mock.Anything).Return(expErr).Once() + require.ErrorIs(tt, expErr, forked.SaveAndApplyConfig(ctx, &apimodels.PostableUserConfig{})) + + // An error in the internal Alertmanager should not be returned. + internal, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().SaveAndApplyConfig(ctx, mock.Anything).Return(nil).Once() + internal.EXPECT().SaveAndApplyConfig(ctx, mock.Anything).Return(expErr).Once() + require.NoError(tt, forked.SaveAndApplyConfig(ctx, &apimodels.PostableUserConfig{})) + }) + t.Run("GetStatus", func(tt *testing.T) { // We care about the status of the remote Alertmanager. _, remote, forked := genTestAlertmanagers(tt, modeRemotePrimary) diff --git a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go index 1a360e4f513..8c2068e8179 100644 --- a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go @@ -34,12 +34,23 @@ func (fam *RemotePrimaryForkedAlertmanager) ApplyConfig(ctx context.Context, con } if err := fam.internal.ApplyConfig(ctx, config); err != nil { + // An error in the internal Alertmanager shouldn't make the whole operation fail. + // We're replicating writes in the internal Alertmanager just for comparing and in case we need to roll back. fam.log.Error("Error applying config to the internal Alertmanager", "err", err) } return nil } func (fam *RemotePrimaryForkedAlertmanager) SaveAndApplyConfig(ctx context.Context, config *apimodels.PostableUserConfig) error { + if err := fam.remote.SaveAndApplyConfig(ctx, config); err != nil { + return err + } + + if err := fam.internal.SaveAndApplyConfig(ctx, config); err != nil { + // An error in the internal Alertmanager shouldn't make the whole operation fail. + // We're replicating writes in the internal Alertmanager just for comparing and in case we need to roll back. + fam.log.Error("Error applying config to the internal Alertmanager", "err", err) + } return nil } @@ -49,6 +60,8 @@ func (fam *RemotePrimaryForkedAlertmanager) SaveAndApplyDefaultConfig(ctx contex } if err := fam.internal.SaveAndApplyDefaultConfig(ctx); err != nil { + // An error in the internal Alertmanager shouldn't make the whole operation fail. + // We're replicating writes in the internal Alertmanager just for comparing and in case we need to roll back. fam.log.Error("Error applying the default configuration to the internal Alertmanager", "err", err) } return nil From bf153294928ca4fcc06f152b58f0b2591a9d2f05 Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Tue, 23 Apr 2024 16:50:16 +0300 Subject: [PATCH 055/222] SSO: run the validation on upsert with all secrets in settings (#86579) * run the validation on upsert with all secrets in settings * rename social to reloadable --- .../ssosettings/ssosettingsimpl/service.go | 54 ++++++----- .../ssosettingsimpl/service_test.go | 89 ++++++++++++++++++- 2 files changed, 116 insertions(+), 27 deletions(-) diff --git a/pkg/services/ssosettings/ssosettingsimpl/service.go b/pkg/services/ssosettings/ssosettingsimpl/service.go index 38862651e74..b3edf2910a0 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service.go @@ -192,24 +192,28 @@ func (s *Service) Upsert(ctx context.Context, settings *models.SSOSettings, requ return ssosettings.ErrNotConfigurable } - social, ok := s.reloadables[settings.Provider] + reloadable, ok := s.reloadables[settings.Provider] if !ok { return ssosettings.ErrInvalidProvider.Errorf("provider %s not found in reloadables", settings.Provider) } - err := social.Validate(ctx, *settings, requester) - if err != nil { - return err - } - storedSettings, err := s.GetForProvider(ctx, settings.Provider) if err != nil { return err } - secrets := collectSecrets(settings, storedSettings) + settingsWithSecrets, err := mergeSecrets(settings.Settings, storedSettings.Settings) + if err != nil { + return err + } + settings.Settings = settingsWithSecrets - settings.Settings, err = s.encryptSecrets(ctx, settings.Settings, storedSettings.Settings) + err = reloadable.Validate(ctx, *settings, requester) + if err != nil { + return err + } + + settings.Settings, err = s.encryptSecrets(ctx, settings.Settings) if err != nil { return err } @@ -221,9 +225,9 @@ func (s *Service) Upsert(ctx context.Context, settings *models.SSOSettings, requ // make a copy of current settings for reload operation and apply overrides reloadSettings := *settings - reloadSettings.Settings = overrideMaps(storedSettings.Settings, settings.Settings, secrets) + reloadSettings.Settings = overrideMaps(storedSettings.Settings, settingsWithSecrets) - go s.reload(social, settings.Provider, reloadSettings) + go s.reload(reloadable, settings.Provider, reloadSettings) return nil } @@ -317,7 +321,7 @@ func (s *Service) getFallbackStrategyFor(provider string) (ssosettings.FallbackS return nil, false } -func (s *Service) encryptSecrets(ctx context.Context, settings, storedSettings map[string]any) (map[string]any, error) { +func (s *Service) encryptSecrets(ctx context.Context, settings map[string]any) (map[string]any, error) { result := make(map[string]any) for k, v := range settings { if isSecret(k) && v != "" { @@ -326,10 +330,6 @@ func (s *Service) encryptSecrets(ctx context.Context, settings, storedSettings m return result, fmt.Errorf("failed to encrypt %s setting because it is not a string: %v", k, v) } - if !isNewSecretValue(strValue) { - strValue = storedSettings[k].(string) - } - encryptedSecret, err := s.secrets.Encrypt(ctx, []byte(strValue), secrets.WithoutScope()) if err != nil { return result, err @@ -483,20 +483,26 @@ func mergeSettings(storedSettings, systemSettings map[string]any) map[string]any return settings } -// collectSecrets collects all the secrets from the request and the currently stored settings -// and returns a new map -func collectSecrets(settings *models.SSOSettings, storedSettings *models.SSOSettings) map[string]any { - secrets := map[string]any{} - for k, v := range settings.Settings { +// mergeSecrets returns a new map with the current value for secrets that have not been updated +func mergeSecrets(settings map[string]any, storedSettings map[string]any) (map[string]any, error) { + settingsWithSecrets := map[string]any{} + for k, v := range settings { if isSecret(k) { - if isNewSecretValue(v.(string)) { - secrets[k] = v.(string) // use the new value + strValue, ok := v.(string) + if !ok { + return nil, fmt.Errorf("secret value is not a string") + } + + if isNewSecretValue(strValue) { + settingsWithSecrets[k] = strValue // use the new value continue } - secrets[k] = storedSettings.Settings[k] // keep the currently stored value + settingsWithSecrets[k] = storedSettings[k] // keep the currently stored value + } else { + settingsWithSecrets[k] = v } } - return secrets + return settingsWithSecrets, nil } func overrideMaps(maps ...map[string]any) map[string]any { diff --git a/pkg/services/ssosettings/ssosettingsimpl/service_test.go b/pkg/services/ssosettings/ssosettingsimpl/service_test.go index 52c2ae69027..0c6fde35b18 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service_test.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service_test.go @@ -979,6 +979,29 @@ func TestService_Upsert(t *testing.T) { require.Error(t, err) }) + t.Run("returns error if a secret does not have the type string", func(t *testing.T) { + t.Parallel() + + env := setupTestEnv(t, false, false, nil) + + provider := social.OktaProviderName + settings := models.SSOSettings{ + Provider: provider, + Settings: map[string]any{ + "client_id": "client-id", + "client_secret": 123, + "enabled": true, + }, + IsDeleted: false, + } + + reloadable := ssosettingstests.NewMockReloadable(t) + env.reloadables[provider] = reloadable + + err := env.service.Upsert(context.Background(), &settings, &user.SignedInUser{}) + require.Error(t, err) + }) + t.Run("returns error if secrets encryption failed", func(t *testing.T) { t.Parallel() @@ -1027,8 +1050,15 @@ func TestService_Upsert(t *testing.T) { }, } + expected := settings + expected.Settings = make(map[string]any) + for key, value := range settings.Settings { + expected.Settings[key] = value + } + expected.Settings["client_secret"] = "encrypted-client-secret" + reloadable := ssosettingstests.NewMockReloadable(t) - reloadable.On("Validate", mock.Anything, settings, mock.Anything).Return(nil) + reloadable.On("Validate", mock.Anything, expected, mock.Anything).Return(nil) reloadable.On("Reload", mock.Anything, mock.Anything).Return(nil).Maybe() env.reloadables[provider] = reloadable env.secrets.On("Decrypt", mock.Anything, []byte("current-client-secret"), mock.Anything).Return([]byte("encrypted-client-secret"), nil).Once() @@ -1037,8 +1067,61 @@ func TestService_Upsert(t *testing.T) { err := env.service.Upsert(context.Background(), &settings, &user.SignedInUser{}) require.NoError(t, err) - settings.Settings["client_secret"] = base64.RawStdEncoding.EncodeToString([]byte("current-client-secret")) - require.EqualValues(t, settings, env.store.ActualSSOSettings) + expected.Settings["client_secret"] = base64.RawStdEncoding.EncodeToString([]byte("current-client-secret")) + require.EqualValues(t, expected, env.store.ActualSSOSettings) + }) + + t.Run("run validation with all new and current secrets available in settings", func(t *testing.T) { + t.Parallel() + + env := setupTestEnv(t, false, false, nil) + + provider := social.AzureADProviderName + settings := models.SSOSettings{ + Provider: provider, + Settings: map[string]any{ + "client_secret": setting.RedactedPassword, + "private_key": setting.RedactedPassword, + "certificate": "new-certificate", + }, + IsDeleted: false, + } + + env.store.ExpectedSSOSetting = &models.SSOSettings{ + Provider: provider, + Settings: map[string]any{ + "client_secret": base64.RawStdEncoding.EncodeToString([]byte("encrypted-current-client-secret")), + "private_key": base64.RawStdEncoding.EncodeToString([]byte("encrypted-current-private-key")), + "certificate": base64.RawStdEncoding.EncodeToString([]byte("encrypted-current-certificate")), + }, + } + + expected := settings + expected.Settings = make(map[string]any) + for key, value := range settings.Settings { + expected.Settings[key] = value + } + expected.Settings["client_secret"] = "current-client-secret" + expected.Settings["private_key"] = "current-private-key" + + reloadable := ssosettingstests.NewMockReloadable(t) + reloadable.On("Validate", mock.Anything, expected, mock.Anything).Return(nil) + reloadable.On("Reload", mock.Anything, mock.Anything).Return(nil).Maybe() + env.reloadables[provider] = reloadable + env.secrets.On("Decrypt", mock.Anything, []byte("encrypted-current-client-secret"), mock.Anything).Return([]byte("current-client-secret"), nil).Once() + env.secrets.On("Decrypt", mock.Anything, []byte("encrypted-current-private-key"), mock.Anything).Return([]byte("current-private-key"), nil).Once() + env.secrets.On("Decrypt", mock.Anything, []byte("encrypted-current-certificate"), mock.Anything).Return([]byte("current-certificate"), nil).Once() + env.secrets.On("Encrypt", mock.Anything, []byte("current-client-secret"), mock.Anything).Return([]byte("encrypted-current-client-secret"), nil).Once() + env.secrets.On("Encrypt", mock.Anything, []byte("current-private-key"), mock.Anything).Return([]byte("encrypted-current-private-key"), nil).Once() + env.secrets.On("Encrypt", mock.Anything, []byte("new-certificate"), mock.Anything).Return([]byte("encrypted-new-certificate"), nil).Once() + + err := env.service.Upsert(context.Background(), &settings, &user.SignedInUser{}) + require.NoError(t, err) + + expected.Settings["client_secret"] = base64.RawStdEncoding.EncodeToString([]byte("encrypted-current-client-secret")) + expected.Settings["private_key"] = base64.RawStdEncoding.EncodeToString([]byte("encrypted-current-private-key")) + expected.Settings["certificate"] = base64.RawStdEncoding.EncodeToString([]byte("encrypted-new-certificate")) + require.EqualValues(t, expected, env.store.ActualSSOSettings) }) t.Run("returns error if store failed to upsert settings", func(t *testing.T) { From a1321d17ca7f07e77aec63ebb049015229756e30 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Tue, 23 Apr 2024 14:50:42 +0100 Subject: [PATCH 056/222] TimeOfDayPicker: Fix text colours in light mode (#86771) fix text colours in light mode --- .../src/components/DateTimePickers/TimeOfDayPicker.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx index 7e0a4d848a8..13c5d69b685 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeOfDayPicker.tsx @@ -95,6 +95,7 @@ const getStyles = (theme: GrafanaTheme2) => { '.rc-time-picker-panel-select': { fontSize: '14px', backgroundColor: bgColor, + color: theme.colors.text.secondary, borderColor, li: { outlineWidth: '2px', @@ -102,10 +103,12 @@ const getStyles = (theme: GrafanaTheme2) => { backgroundColor: 'inherit', border: `1px solid ${theme.v1.palette.orange}`, borderRadius, + color: theme.colors.text.primary, }, '&:hover': { background: optionBgHover, + color: theme.colors.text.primary, }, '&.rc-time-picker-panel-select-option-disabled': { @@ -140,6 +143,7 @@ const getStyles = (theme: GrafanaTheme2) => { backgroundColor: bgColor, borderRadius, borderColor, + color: theme.colors.text.primary, height: theme.spacing(4), '&:focus': getFocusStyles(theme), From 3dabc3ff5d06a41a12abbfe57bc84ab7d76effb8 Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Tue, 23 Apr 2024 16:55:01 +0300 Subject: [PATCH 057/222] SSO: filter out SAML from the SSO providers in UI (#86768) * filter out saml from sso providers * fix lint error --- public/app/features/auth-config/AuthProvidersListPage.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/app/features/auth-config/AuthProvidersListPage.tsx b/public/app/features/auth-config/AuthProvidersListPage.tsx index 1bf44fbf8bb..ceece9d32ea 100644 --- a/public/app/features/auth-config/AuthProvidersListPage.tsx +++ b/public/app/features/auth-config/AuthProvidersListPage.tsx @@ -52,6 +52,9 @@ export const AuthConfigPageUnconnected = ({ reportInteraction('authentication_ui_provider_clicked', { provider: providerType, enabled }); }; + // filter out saml from sso providers because it is already included in availableProviders + providers = providers.filter((p) => p.provider !== 'saml'); + const providerList = availableProviders.length ? [ ...availableProviders.map((p) => ({ From 2fb38a34ac93655d756e26ec49432f6f1a3e22ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Tue, 23 Apr 2024 15:56:04 +0200 Subject: [PATCH 058/222] Navigation: Add a return to previous button when navigating to different sections (#86764) --- .../configure-grafana/feature-toggles/index.md | 2 +- pkg/services/featuremgmt/registry.go | 3 ++- pkg/services/featuremgmt/toggles_gen.csv | 2 +- pkg/services/featuremgmt/toggles_gen.json | 9 ++++++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 9e04b40cf6a..4a9c62d7e94 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -26,6 +26,7 @@ Some features are enabled by default. You can disable these feature by setting t | `featureHighlights` | Highlight Grafana Enterprise features | | | `correlations` | Correlations page | | | `exploreContentOutline` | Content outline sidebar | Yes | +| `returnToPrevious` | Enables the return to previous context functionality | Yes | | `cloudWatchCrossAccountQuerying` | Enables cross-account querying in CloudWatch datasources | Yes | | `nestedFolders` | Enable folder nesting | Yes | | `nestedFolderPicker` | Enables the new folder picker to work with nested folders. Requires the nestedFolders feature toggle | Yes | @@ -74,7 +75,6 @@ Some features are enabled by default. You can disable these feature by setting t | `autoMigrateStatPanel` | Migrate old stat panel to supported stat panel - broken out from autoMigrateOldPanels to enable granular tracking | | `autoMigrateXYChartPanel` | Migrate old XYChart panel to new XYChart2 model | | `disableAngular` | Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime. | -| `returnToPrevious` | Enables the return to previous context functionality | | `grpcServer` | Run the GRPC server | | `accessControlOnCall` | Access control primitives for OnCall | | `alertingNoNormalState` | Stop maintaining state of alerts that are not firing | diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index f6220d8dba3..81948562368 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -208,8 +208,9 @@ var ( { Name: "returnToPrevious", Description: "Enables the return to previous context functionality", - Stage: FeatureStagePublicPreview, + Stage: FeatureStageGeneralAvailability, FrontendOnly: true, + Expression: "true", // enabled by default Owner: grafanaFrontendPlatformSquad, }, { diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index d5a41c349fc..8527a1865ac 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -25,7 +25,7 @@ scenes,experimental,@grafana/dashboards-squad,false,false,true disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,false,true,false logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,false,false,false topnav,deprecated,@grafana/grafana-frontend-platform,false,false,false -returnToPrevious,preview,@grafana/grafana-frontend-platform,false,false,true +returnToPrevious,GA,@grafana/grafana-frontend-platform,false,false,true grpcServer,preview,@grafana/grafana-app-platform-squad,false,false,false unifiedStorage,experimental,@grafana/grafana-app-platform-squad,true,true,false cloudWatchCrossAccountQuerying,GA,@grafana/aws-datasources,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 637ae216736..9ee35efc3e3 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -201,12 +201,15 @@ { "metadata": { "name": "returnToPrevious", - "resourceVersion": "1713545444177", - "creationTimestamp": "2024-04-19T16:50:44Z" + "resourceVersion": "1713870623848", + "creationTimestamp": "2024-04-19T16:50:44Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-04-23 11:10:23.848446 +0000 UTC" + } }, "spec": { "description": "Enables the return to previous context functionality", - "stage": "preview", + "stage": "GA", "codeowner": "@grafana/grafana-frontend-platform", "frontend": true } From 2049f766c6729f20d04b0eaeb3db313aa5eace60 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Tue, 23 Apr 2024 15:15:52 +0100 Subject: [PATCH 059/222] Remove fmt.Println of client certificate (#86773) --- pkg/services/datasources/service/datasource.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/services/datasources/service/datasource.go b/pkg/services/datasources/service/datasource.go index 37c493e3b1a..ac2cb09a6f6 100644 --- a/pkg/services/datasources/service/datasource.go +++ b/pkg/services/datasources/service/datasource.go @@ -595,7 +595,6 @@ func (s *Service) dsTLSOptions(ctx context.Context, ds *datasources.DataSource) if tlsClientAuth { if val, exists, err := s.DecryptedValue(ctx, ds, "tlsClientCert"); err == nil { - fmt.Print("\n\n\n\n", val, exists, err, "\n\n\n\n") if exists && len(val) > 0 { opts.ClientCertificate = val } From 173ba73a8a5280cac48ef2414ba1355097f0c426 Mon Sep 17 00:00:00 2001 From: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> Date: Tue, 23 Apr 2024 07:54:42 -0700 Subject: [PATCH 060/222] Canvas: Ensure tangency for first segment (#86543) --- .../components/connections/ConnectionSVG.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx b/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx index 5c15496e1c2..b192cb1ca0c 100644 --- a/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx +++ b/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx @@ -196,7 +196,7 @@ export const ConnectionSVG = ({ const Yn = vertices[index + 1].y * yDist + yStart; if (index === 0) { // First vertex - angle1 = calculateAngle(xStart, yStart, X, Y); + angle1 = calculateAngle(x1, y1, X, Y); angle2 = calculateAngle(X, Y, Xn, Yn); } else { // All vertices @@ -239,7 +239,7 @@ export const ConnectionSVG = ({ // Only calculate arcs if there is a radius if (radius) { // Length of segment - const lSegment = calculateDistance(X, Y, xStart, yStart); + const lSegment = calculateDistance(X, Y, x1, y1); if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegment)) { // Limit curve control points to mid segment lHalfArc = 0.5 * lSegment; @@ -250,8 +250,8 @@ export const ConnectionSVG = ({ if (index < vertices.length - 1) { // Not also the last point const nextVertex = vertices[index + 1]; - Xn = nextVertex.x * xDist + xStart; - Yn = nextVertex.y * yDist + yStart; + Xn = nextVertex.x * xDist + x1; + Yn = nextVertex.y * yDist + y1; } // Length of next segment @@ -262,15 +262,15 @@ export const ConnectionSVG = ({ } // Calculate arc control points const lDelta = lSegment - lHalfArc; - xa = lDelta * Math.cos(angle1) + xStart; - ya = lDelta * Math.sin(angle1) + yStart; + xa = lDelta * Math.cos(angle1) + x1; + ya = lDelta * Math.sin(angle1) + y1; xb = lHalfArc * Math.cos(angle2) + X; yb = lHalfArc * Math.sin(angle2) + Y; // Check if arc control points are inside of segment, otherwise swap sign - if ((xa > X && xa > xStart) || (xa < X && xa < xStart)) { - xa = (lDelta + 2 * lHalfArc) * Math.cos(angle1) + xStart; - ya = (lDelta + 2 * lHalfArc) * Math.sin(angle1) + yStart; + if ((xa > X && xa > x1) || (xa < X && xa < x1)) { + xa = (lDelta + 2 * lHalfArc) * Math.cos(angle1) + x1; + ya = (lDelta + 2 * lHalfArc) * Math.sin(angle1) + y1; xb = -lHalfArc * Math.cos(angle2) + X; yb = -lHalfArc * Math.sin(angle2) + Y; } From 9553923eb72fcf4dff0b90bd96e7135041b7b38e Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Tue, 23 Apr 2024 11:36:34 -0400 Subject: [PATCH 061/222] Chore: Fix failing ssosettingimpl test (#86792) Chore: Fix failing ssosetting test --- pkg/services/ssosettings/ssosettingsimpl/service_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/services/ssosettings/ssosettingsimpl/service_test.go b/pkg/services/ssosettings/ssosettingsimpl/service_test.go index 0c6fde35b18..7a9bd0919ad 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service_test.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service_test.go @@ -982,7 +982,7 @@ func TestService_Upsert(t *testing.T) { t.Run("returns error if a secret does not have the type string", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.OktaProviderName settings := models.SSOSettings{ @@ -1074,7 +1074,7 @@ func TestService_Upsert(t *testing.T) { t.Run("run validation with all new and current secrets available in settings", func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, nil) + env := setupTestEnv(t, false, false, false) provider := social.AzureADProviderName settings := models.SSOSettings{ From 18a4c56539f39db2759b5e6587519ea53bdec5b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 11:58:17 -0400 Subject: [PATCH 062/222] Chore(deps): Bump go.opentelemetry.io/collector/pdata from 1.0.1 to 1.5.0 (#86091) * Chore(deps): Bump go.opentelemetry.io/collector/pdata Bumps [go.opentelemetry.io/collector/pdata](https://github.com/open-telemetry/opentelemetry-collector) from 1.0.1 to 1.5.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-collector/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-collector/blob/main/CHANGELOG-API.md) - [Commits](https://github.com/open-telemetry/opentelemetry-collector/compare/pdata/v1.0.1...pdata/v1.5.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/collector/pdata dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * go work sync --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kristin Laemmert --- go.mod | 4 ++-- go.sum | 8 ++++---- pkg/apimachinery/go.mod | 2 +- pkg/apimachinery/go.sum | 3 +-- pkg/apiserver/go.mod | 2 +- pkg/apiserver/go.sum | 3 +-- pkg/promlib/go.mod | 2 +- pkg/promlib/go.sum | 3 +-- 8 files changed, 12 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index ef4b86c7da1..bdb21a4f486 100644 --- a/go.mod +++ b/go.mod @@ -88,14 +88,14 @@ require ( github.com/urfave/cli/v2 v2.25.0 // @grafana/grafana-backend-group github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae // @grafana/grafana-backend-group github.com/yudai/gojsondiff v1.0.0 // @grafana/grafana-backend-group - go.opentelemetry.io/collector/pdata v1.0.1 // @grafana/grafana-backend-group + go.opentelemetry.io/collector/pdata v1.5.0 // @grafana/grafana-backend-group go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0 // @grafana/grafana-operator-experience-squad go.opentelemetry.io/otel/exporters/jaeger v1.10.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/sdk v1.24.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/trace v1.24.0 // @grafana/grafana-backend-group golang.org/x/crypto v0.21.0 // @grafana/grafana-backend-group golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // @grafana/alerting-squad-backend - golang.org/x/net v0.22.0 // @grafana/oss-big-tent @grafana/partner-datasources + golang.org/x/net v0.23.0 // @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.18.0 // @grafana/identity-access-team golang.org/x/sync v0.6.0 // @grafana/alerting-squad-backend golang.org/x/time v0.5.0 // @grafana/grafana-backend-group diff --git a/go.sum b/go.sum index 75b558532fa..367f004494a 100644 --- a/go.sum +++ b/go.sum @@ -3167,8 +3167,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/collector/featuregate v1.0.0/go.mod h1:xGbRuw+GbutRtVVSEy3YR2yuOlEyiUMhN2M9DJljgqY= go.opentelemetry.io/collector/pdata v1.0.0/go.mod h1:TsDFgs4JLNG7t6x9D8kGswXUz4mme+MyNChHx8zSF6k= -go.opentelemetry.io/collector/pdata v1.0.1 h1:dGX2h7maA6zHbl5D3AsMnF1c3Nn+3EUftbVCLzeyNvA= -go.opentelemetry.io/collector/pdata v1.0.1/go.mod h1:jutXeu0QOXYY8wcZ/hege+YAnSBP3+jpTqYU1+JTI5Y= +go.opentelemetry.io/collector/pdata v1.5.0 h1:1fKTmUpr0xCOhP/B0VEvtz7bYPQ45luQ8XFyA07j8LE= +go.opentelemetry.io/collector/pdata v1.5.0/go.mod h1:TYj8aKRWZyT/KuKQXKyqSEvK/GV+slFaDMEI+Ke64Yw= go.opentelemetry.io/collector/semconv v0.90.1/go.mod h1:j/8THcqVxFna1FpvA2zYIsUperEtOaRaqoLYIN4doWw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= @@ -3473,8 +3473,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index c2e41f53e24..59e662cd1f6 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -25,7 +25,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/testify v1.9.0 // indirect - golang.org/x/net v0.22.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index c82e0a06cc0..4b910c32796 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -62,8 +62,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 1fbc37c28f5..666a7d0d349 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -126,7 +126,7 @@ require ( go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect - golang.org/x/net v0.22.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 0efc1c878d3..6d9c4de812b 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -390,8 +390,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 012c44cb4c1..c726e5eb033 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -104,7 +104,7 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.3.0 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.22.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.17.0 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 717ed270802..09815115d7d 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -331,8 +331,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= From ca7f41be11ae9dfbb9fae558cd981cc148f9a73a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 12:02:36 -0400 Subject: [PATCH 063/222] Chore(deps): Bump buf.build/gen/go/parca-dev/parca/protocolbuffers/go from 1.28.1-20221222094228-8b1d3d0f62e6.4 to 1.33.0-20240414232344-9ca06271cb73.1 (#86092) Chore(deps): Bump buf.build/gen/go/parca-dev/parca/protocolbuffers/go --- updated-dependencies: - dependency-name: buf.build/gen/go/parca-dev/parca/protocolbuffers/go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index bdb21a4f486..9d12fba44f6 100644 --- a/go.mod +++ b/go.mod @@ -237,7 +237,7 @@ require ( require ( buf.build/gen/go/parca-dev/parca/bufbuild/connect-go v1.4.1-20221222094228-8b1d3d0f62e6.1 // @grafana/observability-traces-and-profiling - buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.28.1-20221222094228-8b1d3d0f62e6.4 // @grafana/observability-traces-and-profiling + buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.33.0-20240414232344-9ca06271cb73.1 // @grafana/observability-traces-and-profiling github.com/Masterminds/semver/v3 v3.1.1 // @grafana/grafana-release-guild github.com/alicebob/miniredis/v2 v2.30.1 // @grafana/alerting-squad-backend github.com/dave/dst v0.27.2 // @grafana/grafana-as-code diff --git a/go.sum b/go.sum index 367f004494a..56a33017cc0 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,7 @@ -buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.28.1-20221127060915-a1ecdc58eccd.4/go.mod h1:92ejKVTiuvnKoAtRlpJpIxKfloI935DDqhs0NCRx+KM= buf.build/gen/go/parca-dev/parca/bufbuild/connect-go v1.4.1-20221222094228-8b1d3d0f62e6.1 h1:wQ75SnlaD0X30PnrmA+07A/5fnQWrAHy1mzv+CPB5Oo= buf.build/gen/go/parca-dev/parca/bufbuild/connect-go v1.4.1-20221222094228-8b1d3d0f62e6.1/go.mod h1:VYzBTKhjl92cl3sv+xznQcJHCezU7qnI0FhBAUb4n8c= -buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.28.1-20221222094228-8b1d3d0f62e6.4 h1:3ThI7dcndwVLimMCxuiaVqMJEx5FfMXydD7flXlnDkQ= -buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.28.1-20221222094228-8b1d3d0f62e6.4/go.mod h1:7dY08PsClUI7xt/6lEMJERgOcdf3d5Gnchm8qScIhRg= +buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.33.0-20240414232344-9ca06271cb73.1 h1:arEscdzM2EZPZT8x7tSzuBVgyZrnsKuvoftapClxUgw= +buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.33.0-20240414232344-9ca06271cb73.1/go.mod h1:/vvnaG5MGgbuJxYTucpo0QOom9xbSb6Y43za3/as9qk= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= From 37d086d49cf4ead0a76ca5863ae652c7460b0cdf Mon Sep 17 00:00:00 2001 From: Kyle Cunningham Date: Tue, 23 Apr 2024 11:03:24 -0500 Subject: [PATCH 064/222] Table Panel: Fix images not showing on hover with multiple data links (#86732) * Fix issue * Prettier * codeincarnate/image-table-fix/ lint * Make linter happy by using div with role of button + improve a11y --------- Co-authored-by: jev forsberg Co-authored-by: nmarrs --- .../src/components/Table/ImageCell.tsx | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/grafana-ui/src/components/Table/ImageCell.tsx b/packages/grafana-ui/src/components/Table/ImageCell.tsx index d4a164cebce..6732bb6c54f 100644 --- a/packages/grafana-ui/src/components/Table/ImageCell.tsx +++ b/packages/grafana-ui/src/components/Table/ImageCell.tsx @@ -1,9 +1,6 @@ -import { cx } from '@emotion/css'; import React from 'react'; -import { useStyles2 } from '../../themes'; import { getCellLinks } from '../../utils'; -import { Button, clearLinkButtonStyles } from '../Button'; import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu'; import { TableCellProps } from './types'; @@ -16,7 +13,6 @@ export const ImageCell = (props: TableCellProps) => { const displayValue = field.display!(cell.value); const hasLinks = Boolean(getCellLinks(field, row)?.length); - const clearButtonStyle = useStyles2(clearLinkButtonStyles); return (
@@ -27,12 +23,29 @@ export const ImageCell = (props: TableCellProps) => { links={() => getCellLinks(field, row) || []} > {(api) => { - const img = ; + const img = ( + + ); if (api.openMenu) { return ( - +
); } else { return img; From 709d78b8b513e28f642cb24d939a108e6956362a Mon Sep 17 00:00:00 2001 From: Nathan Marrs Date: Tue, 23 Apr 2024 10:24:02 -0600 Subject: [PATCH 065/222] Chore: Fix CVE-2024-22363 (#86738) --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 090b3230876..0db6125d884 100644 --- a/package.json +++ b/package.json @@ -411,7 +411,7 @@ "uuid": "9.0.1", "visjs-network": "4.25.0", "whatwg-fetch": "3.6.20", - "xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz" + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz" }, "resolutions": { "underscore": "1.13.6", diff --git a/yarn.lock b/yarn.lock index b94dd695064..bd09e23891c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18911,7 +18911,7 @@ __metadata: webpack-merge: "npm:5.10.0" webpackbar: "npm:^6.0.0" whatwg-fetch: "npm:3.6.20" - xlsx: "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz" + xlsx: "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz" yaml: "npm:^2.0.0" yargs: "npm:^17.5.1" dependenciesMeta: @@ -32146,12 +32146,12 @@ __metadata: languageName: node linkType: hard -"xlsx@https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz": - version: 0.19.1 - resolution: "xlsx@https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz" +"xlsx@https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz": + version: 0.20.2 + resolution: "xlsx@https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz" bin: xlsx: ./bin/xlsx.njs - checksum: 10/d84f81bec6501f26728764c207a88a3676c8979e801d5be4d60500859bf4d799256dc07823e6fb3fa404f583aa11c55071483c1f76372416588a643a92ab06cf + checksum: 10/2d8e0644888f90fa9145ea74ed90b844154ce89c4f0e4f92fcce3f224fa71654da99aa48d99d65ba86eb0632a4858ba2dea7eef8b54fd8bd23954a09d1884aa1 languageName: node linkType: hard From 14789db1b73018b86b6416e6d4bac080a24e3668 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Tue, 23 Apr 2024 18:24:25 +0200 Subject: [PATCH 066/222] Docs: add `Provisioning` section to `Install on Kubernetes` docs (#83875) * Docs: add `Provisioning` section to `Install on Kubernetes` docs * prefix k8s object names with `grafana-` * Fix `suplying` typo --- .../installation/kubernetes/index.md | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/docs/sources/setup-grafana/installation/kubernetes/index.md b/docs/sources/setup-grafana/installation/kubernetes/index.md index 5db13ecea67..d6ca75911bb 100644 --- a/docs/sources/setup-grafana/installation/kubernetes/index.md +++ b/docs/sources/setup-grafana/installation/kubernetes/index.md @@ -499,6 +499,98 @@ By default, Kubernetes deployment rollout history remains in the system so that If you need to go back to any other `REVISION`, just repeat the steps above and use the correct revision number in the `--to-revision` parameter. +## Provision Grafana resources using configuration files + +Provisioning can add, update, or delete resources specified in your configuration files when Grafana starts. For detailed information, refer to [Grafana Provisioning](/docs/grafana//administration/provisioning). + +This section outlines general instructions for provisioning Grafana resources within Kubernetes, using a persistent volume to supply the configuration files to the Grafana pod. + +1. Add a new `PersistentVolumeClaim` to the `grafana.yaml` file. + + ```yaml + --- + apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: grafana-provisioning-pvc + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Mi + ``` + +1. In the `grafana.yaml` file, mount the persistent volume into `/etc/grafana/provisioning` as follows. + + ```yaml + ... + volumeMounts: + - mountPath: /etc/grafana/provisioning + name: grafana-provisioning-pv + ... + volumes: + - name: grafana-provisioning-pv + persistentVolumeClaim: + claimName: grafana-provisioning-pvc + ... + ``` + +1. Find or create the provision resources you want to add. For instance, create a `alerting.yaml` file adding a mute timing (alerting resource). + + ```yaml + apiVersion: 1 + muteTimes: + - orgId: 1 + name: MuteWeekends + time_intervals: + - weekdays: [saturday, sunday] + ``` + +1. By default, configuration files for alerting resources need to be placed in the `provisioning/alerting` directory. + + Save the `alerting.yaml` file in a directory named `alerting`, as we will next supply this `alerting` directory to the `/etc/grafana/provisioning` folder of the Grafana pod. + +1. Verify first the content of the provisioning directory in the running Grafana pod. + + ```bash + kubectl exec -n my-grafana -- ls /etc/grafana/provisioning/ + ``` + + ```bash + kubectl exec -n my-grafana -- ls /etc/grafana/provisioning/alerting + ``` + + Because the `alerting` folder is not available yet, the last command should output a `No such file or directory` error. + +1. Copy the local `alerting` directory to `/etc/grafana/provisioning/` in the Grafana pod. + + ```bash + kubectl cp alerting my-grafana/:/etc/grafana/provisioning/ + ``` + + You can follow the same process to provision additional Grafana resources by supplying the following folders: + + - `provisioning/dashboards` + - `provisioning/datasources` + - `provisioning/plugins` + +1. Verify the `alerting` directory in the running Grafana pod includes the `alerting.yaml` file. + + ```bash + kubectl exec -n my-grafana -- ls /etc/grafana/provisioning/alerting + ``` + +1. Restart the Grafana pod to provision the resources. + + ```bash + kubectl rollout restart -n my-grafana deployment --selector=app=grafana + ``` + + Note that `rollout restart` kills the previous pod and scales a new pod. When the old pod terminates, you may have to enable port-forwarding in the new pod. For instructions, refer to the previous sections about port forwarding in this guide. + +1. Verify the Grafana resources are properly provisioned within the Grafana instance. + ## Troubleshooting This section includes troubleshooting tips you might find helpful when deploying Grafana on Kubernetes. From 2c5b684b8598d66f430378e57e695761c141728a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 12:32:16 -0400 Subject: [PATCH 067/222] Chore(deps): Bump google.golang.org/api from 0.162.0 to 0.176.0 (#86794) * Chore(deps): Bump google.golang.org/api from 0.162.0 to 0.176.0 Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.162.0 to 0.176.0. - [Release notes](https://github.com/googleapis/google-api-go-client/releases) - [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.162.0...v0.176.0) --- updated-dependencies: - dependency-name: google.golang.org/api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * but does it work across all workspaces --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kristin Laemmert --- go.mod | 24 ++++++++++----------- go.sum | 46 +++++++++++++++++++++-------------------- go.work.sum | 13 +++++++++++- pkg/apimachinery/go.mod | 2 +- pkg/apimachinery/go.sum | 2 +- pkg/apiserver/go.mod | 16 +++++++------- pkg/apiserver/go.sum | 40 +++++++---------------------------- pkg/promlib/go.mod | 10 ++++----- pkg/promlib/go.sum | 15 +++++--------- 9 files changed, 74 insertions(+), 94 deletions(-) diff --git a/go.mod b/go.mod index 9d12fba44f6..741049a26d9 100644 --- a/go.mod +++ b/go.mod @@ -93,15 +93,15 @@ require ( go.opentelemetry.io/otel/exporters/jaeger v1.10.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/sdk v1.24.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/trace v1.24.0 // @grafana/grafana-backend-group - golang.org/x/crypto v0.21.0 // @grafana/grafana-backend-group + golang.org/x/crypto v0.22.0 // @grafana/grafana-backend-group golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // @grafana/alerting-squad-backend - golang.org/x/net v0.23.0 // @grafana/oss-big-tent @grafana/partner-datasources - golang.org/x/oauth2 v0.18.0 // @grafana/identity-access-team + golang.org/x/net v0.24.0 // @grafana/oss-big-tent @grafana/partner-datasources + golang.org/x/oauth2 v0.19.0 // @grafana/identity-access-team golang.org/x/sync v0.6.0 // @grafana/alerting-squad-backend golang.org/x/time v0.5.0 // @grafana/grafana-backend-group golang.org/x/tools v0.17.0 // @grafana/grafana-as-code gonum.org/v1/gonum v0.12.0 // @grafana/observability-metrics - google.golang.org/api v0.162.0 // @grafana/grafana-backend-group + google.golang.org/api v0.176.0 // @grafana/grafana-backend-group google.golang.org/grpc v1.63.2 // @grafana/plugins-platform-backend google.golang.org/protobuf v1.33.0 // @grafana/plugins-platform-backend gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect @@ -155,7 +155,7 @@ require ( github.com/golang/protobuf v1.5.4 // @grafana/grafana-backend-group github.com/google/btree v1.1.2 // indirect github.com/google/flatbuffers v23.5.26+incompatible // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // @grafana/grafana-backend-group + github.com/googleapis/gax-go/v2 v2.12.3 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/grafana/grafana-google-sdk-go v0.1.0 // @grafana/partner-datasources github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect; @grafana/plugins-platform-backend @@ -200,10 +200,9 @@ require ( go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.11.0 // @grafana/alerting-squad-backend go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // @grafana/grafana-backend-group golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect; @grafana/grafana-backend-group ) @@ -275,7 +274,7 @@ require github.com/apache/arrow/go/v15 v15.0.2 // @grafana/observability-metrics require ( cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/Azure/azure-pipeline-go v0.2.3 // indirect github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect github.com/Masterminds/goutils v1.1.1 // indirect @@ -358,9 +357,9 @@ require ( go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/term v0.18.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect + golang.org/x/term v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect @@ -382,7 +381,6 @@ require ( ) require ( - cloud.google.com/go/compute v1.24.0 // indirect cloud.google.com/go/iam v1.1.6 // indirect filippo.io/age v1.1.1 // @grafana/identity-access-team github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 // indirect @@ -474,6 +472,8 @@ require github.com/getkin/kin-openapi v0.120.0 // @grafana/grafana-as-code require github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 // @grafana/identity-access-team require ( + cloud.google.com/go/auth v0.2.2 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.1 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/bytedance/sonic v1.9.1 // indirect diff --git a/go.sum b/go.sum index 56a33017cc0..e68ddd110ba 100644 --- a/go.sum +++ b/go.sum @@ -168,6 +168,10 @@ cloud.google.com/go/assuredworkloads v1.11.1/go.mod h1:+F04I52Pgn5nmPG36CWFtxmav cloud.google.com/go/assuredworkloads v1.11.2/go.mod h1:O1dfr+oZJMlE6mw0Bp0P1KZSlj5SghMBvTpZqIcUAW4= cloud.google.com/go/assuredworkloads v1.11.3/go.mod h1:vEjfTKYyRUaIeA0bsGJceFV2JKpVRgyG2op3jfa59Zs= cloud.google.com/go/assuredworkloads v1.11.4/go.mod h1:4pwwGNwy1RP0m+y12ef3Q/8PaiWrIDQ6nD2E8kvWI9U= +cloud.google.com/go/auth v0.2.2 h1:gmxNJs4YZYcw6YvKRtVBaF2fyUE6UrWPyzU8jHvYfmI= +cloud.google.com/go/auth v0.2.2/go.mod h1:2bDNJWtWziDT3Pu1URxHHbkHE/BbOCuyUiKIGcNvafo= +cloud.google.com/go/auth/oauth2adapt v0.2.1 h1:VSPmMmUlT8CkIZ2PzD9AlLN+R3+D1clXMWHHa6vG/Ag= +cloud.google.com/go/auth/oauth2adapt v0.2.1/go.mod h1:tOdK/k+D2e4GEwfBRA48dKNQiDsqIXxLh7VU319eV0g= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= @@ -313,13 +317,12 @@ cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdi cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4hFDcR04Rns= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -2137,8 +2140,9 @@ github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38 github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= 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/gophercloud/gophercloud v1.8.0 h1:TM3Jawprb2NrdOnvcHhWJalmKmAmOGgfZElM/3oBYCk= @@ -3303,8 +3307,8 @@ golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72 golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -3472,8 +3476,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -3515,8 +3519,8 @@ golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -3688,8 +3692,8 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -3711,8 +3715,8 @@ golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -3941,8 +3945,8 @@ google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWL google.golang.org/api v0.139.0/go.mod h1:CVagp6Eekz9CjGZ718Z+sloknzkDJE7Vc1Ckj9+viBk= google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= -google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps= -google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/api v0.176.0 h1:dHj1/yv5Dm/eQTXiP9hNCRT3xzJHWXeNdRq29XbMxoE= +google.golang.org/api v0.176.0/go.mod h1:Rra+ltKu14pps/4xTycZfobMgLpbosoaaL7c+SEMrO8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -3953,8 +3957,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -4150,8 +4152,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go. google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg= google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= @@ -4174,8 +4176,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= diff --git a/go.work.sum b/go.work.sum index 80ad0ed6b81..aacacdeac54 100644 --- a/go.work.sum +++ b/go.work.sum @@ -79,6 +79,8 @@ cloud.google.com/go/clouddms v1.7.4/go.mod h1:RdrVqoFG9RWI5AvZ81SxJ/xvxPdtcRhFot cloud.google.com/go/cloudtasks v1.12.4 h1:5xXuFfAjg0Z5Wb81j2GAbB3e0bwroCeSF+5jBn/L650= cloud.google.com/go/cloudtasks v1.12.6 h1:EUt1hIZ9bLv8Iz9yWaCrqgMnIU+Tdh0yXM1MMVGhjfE= cloud.google.com/go/cloudtasks v1.12.6/go.mod h1:b7c7fe4+TJsFZfDyzO51F7cjq7HLUlRi/KZQLQjDsaY= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/contactcenterinsights v1.12.1 h1:EiGBeejtDDtr3JXt9W7xlhXyZ+REB5k2tBgVPVtmNb0= cloud.google.com/go/contactcenterinsights v1.13.0 h1:6Vs/YnDG5STGjlWMEjN/xtmft7MrOTOnOZYUZtGTx0w= cloud.google.com/go/contactcenterinsights v1.13.0/go.mod h1:ieq5d5EtHsu8vhe2y3amtZ+BE+AQwX5qAy7cpo0POsI= @@ -609,7 +611,6 @@ github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJ github.com/grafana/grafana-plugin-sdk-go v0.215.0/go.mod h1:nBsh3jRItKQUXDF2BQkiQCPxqrsSQeb+7hiFyJTO1RE= github.com/grafana/grafana-plugin-sdk-go v0.216.0/go.mod h1:FdvSvOliqpVLnytM7e89zCFyYPDE6VOn9SIjVQRvVxM= github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo= -github.com/grafana/grafana/pkg/promlib v0.0.5/go.mod h1:iZNjkJBN8DU/5/DxrmwuHaSeiKODT72DYiQ0c9Da1JQ= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= @@ -777,23 +778,33 @@ golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2F golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4= +google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:PVreiBMirk8ypES6aw9d4p6iiBNSIfZEBqr3UGoAi2E= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231120223509-83a465c0220f h1:hL+1ptbhFoeL1HcROQ8OGXaqH0jYRRibgWQWco0/Ugc= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0 h1:Y6QQt9D/syZt/Qgnz5a1y2O3WunQeeVDfS9+Xr82iFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe h1:weYsP+dNijSQVoLAb5bpUos3ciBpNU/NEVlHFKrk8pg= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:IN9OQUXZ0xT+26MDwZL8fJcYw+y99b0eYPA2U15Jt8o= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 59e662cd1f6..c925c5a4d61 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -25,7 +25,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/testify v1.9.0 // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/net v0.24.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 4b910c32796..1d3a05158ac 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -62,7 +62,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 666a7d0d349..b385c350b42 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -124,21 +124,19 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.22.0 // indirect golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/oauth2 v0.19.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.17.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 6d9c4de812b..2d9c92d500a 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -103,8 +103,6 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= @@ -115,7 +113,6 @@ github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZat github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -306,7 +303,6 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= @@ -364,9 +360,7 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= @@ -376,7 +370,6 @@ 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -388,18 +381,14 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -410,28 +399,18 @@ golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -447,7 +426,6 @@ 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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -460,14 +438,12 @@ gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -475,8 +451,6 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index c726e5eb033..06e78c60988 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -104,14 +104,14 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.3.0 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/oauth2 v0.19.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.17.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 09815115d7d..7598421b85b 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -331,10 +331,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -357,8 +356,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= @@ -386,14 +384,11 @@ gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= From fc5007b0d5d04b380ddbbc23b6b7307b5f03b411 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Tue, 23 Apr 2024 19:30:11 +0100 Subject: [PATCH 068/222] Dashboards: Fix minor i18n papercuts that snuck through (#86802) * fix two issues in dashboards i18n * import * fix another --- packages/grafana-e2e-selectors/src/selectors/pages.ts | 2 +- .../dashboard-scene/settings/DeleteDashboardButton.tsx | 7 ++++++- .../app/features/dashboard-scene/sharing/ShareLinkTab.tsx | 2 +- .../components/DeleteDashboard/DeleteDashboardButton.tsx | 5 +++-- .../features/dashboard/components/ShareModal/ShareLink.tsx | 2 +- public/app/features/dashboard/dashgrid/DashboardEmpty.tsx | 3 +-- public/locales/en-US/grafana.json | 6 +++--- public/locales/pseudo-LOCALE/grafana.json | 6 +++--- 8 files changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 3ae6d8dc99c..5368be3830a 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -83,7 +83,7 @@ export const Pages = { close: 'data-testid dashboard-settings-close', }, General: { - deleteDashBoard: 'Dashboard settings page delete dashboard button', + deleteDashBoard: 'data-testid Dashboard settings page delete dashboard button', sectionItems: (item: string) => `Dashboard settings section item ${item}`, saveDashBoard: 'Dashboard settings aside actions Save button', saveAsDashBoard: 'Dashboard settings aside actions Save As button', diff --git a/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx b/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx index a896816dc7a..f1fcc2e5a81 100644 --- a/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx +++ b/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { useAsyncFn, useToggle } from 'react-use'; +import { selectors } from '@grafana/e2e-selectors'; import { Button, ConfirmModal, Modal } from '@grafana/ui'; import { Trans } from 'app/core/internationalization'; @@ -15,7 +16,11 @@ export function DeleteDashboardButton({ dashboard }: ButtonProps) { return ( <> - diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx index 21a739717a0..1f7bd26eacb 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx @@ -220,7 +220,7 @@ function ShareLinkTabRenderer({ model }: SceneComponentProps) { bottomSpacing={0} > - To render a panel image, you must install the + To render a panel image, you must install the{' '} { hideModal, }); }} - aria-label="Dashboard settings page delete dashboard button" + data-testid={selectors.pages.Dashboard.Settings.General.deleteDashBoard} > - Delete Dashboard + Delete dashboard )} diff --git a/public/app/features/dashboard/components/ShareModal/ShareLink.tsx b/public/app/features/dashboard/components/ShareModal/ShareLink.tsx index 3ece45c6de9..b7bc752eab9 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLink.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLink.tsx @@ -163,7 +163,7 @@ export class ShareLink extends PureComponent { bottomSpacing={0} > - To render a panel image, you must install the + To render a panel image, you must install the{' '} Grafana image renderer plugin diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx b/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx index b2fdb9f6267..8ba441de4b3 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty.tsx @@ -138,8 +138,7 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => { - Import dashboards from files or - grafana.com. + Import dashboards from files or grafana.com. diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index b783638694f..bc911111504 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -276,7 +276,7 @@ "add-widget-body": "Create lists, markdowns and other widgets", "add-widget-button": "Add widget", "add-widget-header": "Add a widget", - "import-a-dashboard-body": "Import dashboards from files or<1>grafana.com.", + "import-a-dashboard-body": "Import dashboards from files or <1>grafana.com.", "import-a-dashboard-header": "Import a dashboard", "import-dashboard-button": "Import dashboard" }, @@ -385,7 +385,7 @@ "annotations": { "title": "Annotations" }, - "dashboard-delete-button": "Delete Dashboard", + "dashboard-delete-button": "Delete dashboard", "general": { "auto-refresh-description": "Define the auto refresh intervals that should be available in the auto refresh list. Use the format '5s' for seconds, '1m' for minutes, '1h' for hours, and '1d' for days (e.g.: '5s,10s,30s,1m,5m,15m,30m,1h,2h,1d').", "auto-refresh-label": "Auto refresh", @@ -1524,7 +1524,7 @@ "info-text": "Create a direct link to this dashboard or panel, customized with the options below.", "link-url": "Link URL", "render-alert": "Image renderer plugin not installed", - "render-instructions": "To render a panel image, you must install the<1>Grafana image renderer plugin. Please contact your Grafana administrator to install the plugin.", + "render-instructions": "To render a panel image, you must install the <2>Grafana image renderer plugin. Please contact your Grafana administrator to install the plugin.", "rendered-image": "Direct link rendered image", "save-alert": "Dashboard is not saved", "save-dashboard": "To render a panel image, you must save the dashboard first.", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 9fa2685c1ed..64daa25d135 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -276,7 +276,7 @@ "add-widget-body": "Cřęäŧę ľįşŧş, mäřĸđőŵʼnş äʼnđ őŧĥęř ŵįđģęŧş", "add-widget-button": "Åđđ ŵįđģęŧ", "add-widget-header": "Åđđ ä ŵįđģęŧ", - "import-a-dashboard-body": "Ĩmpőřŧ đäşĥþőäřđş ƒřőm ƒįľęş őř<1>ģřäƒäʼnä.čőm.", + "import-a-dashboard-body": "Ĩmpőřŧ đäşĥþőäřđş ƒřőm ƒįľęş őř <1>ģřäƒäʼnä.čőm.", "import-a-dashboard-header": "Ĩmpőřŧ ä đäşĥþőäřđ", "import-dashboard-button": "Ĩmpőřŧ đäşĥþőäřđ" }, @@ -385,7 +385,7 @@ "annotations": { "title": "Åʼnʼnőŧäŧįőʼnş" }, - "dashboard-delete-button": "Đęľęŧę Đäşĥþőäřđ", + "dashboard-delete-button": "Đęľęŧę đäşĥþőäřđ", "general": { "auto-refresh-description": "Đęƒįʼnę ŧĥę äūŧő řęƒřęşĥ įʼnŧęřväľş ŧĥäŧ şĥőūľđ þę äväįľäþľę įʼn ŧĥę äūŧő řęƒřęşĥ ľįşŧ. Ůşę ŧĥę ƒőřmäŧ '5ş' ƒőř şęčőʼnđş, '1m' ƒőř mįʼnūŧęş, '1ĥ' ƒőř ĥőūřş, äʼnđ '1đ' ƒőř đäyş (ę.ģ.: '5ş,10ş,30ş,1m,5m,15m,30m,1ĥ,2ĥ,1đ').", "auto-refresh-label": "Åūŧő řęƒřęşĥ", @@ -1524,7 +1524,7 @@ "info-text": "Cřęäŧę ä đįřęčŧ ľįʼnĸ ŧő ŧĥįş đäşĥþőäřđ őř päʼnęľ, čūşŧőmįžęđ ŵįŧĥ ŧĥę őpŧįőʼnş þęľőŵ.", "link-url": "Ŀįʼnĸ ŮŖĿ", "render-alert": "Ĩmäģę řęʼnđęřęř pľūģįʼn ʼnőŧ įʼnşŧäľľęđ", - "render-instructions": "Ŧő řęʼnđęř ä päʼnęľ įmäģę, yőū mūşŧ įʼnşŧäľľ ŧĥę<1>Ğřäƒäʼnä įmäģę řęʼnđęřęř pľūģįʼn. Pľęäşę čőʼnŧäčŧ yőūř Ğřäƒäʼnä äđmįʼnįşŧřäŧőř ŧő įʼnşŧäľľ ŧĥę pľūģįʼn.", + "render-instructions": "Ŧő řęʼnđęř ä päʼnęľ įmäģę, yőū mūşŧ įʼnşŧäľľ ŧĥę <2>Ğřäƒäʼnä įmäģę řęʼnđęřęř pľūģįʼn. Pľęäşę čőʼnŧäčŧ yőūř Ğřäƒäʼnä äđmįʼnįşŧřäŧőř ŧő įʼnşŧäľľ ŧĥę pľūģįʼn.", "rendered-image": "Đįřęčŧ ľįʼnĸ řęʼnđęřęđ įmäģę", "save-alert": "Đäşĥþőäřđ įş ʼnőŧ şävęđ", "save-dashboard": "Ŧő řęʼnđęř ä päʼnęľ įmäģę, yőū mūşŧ şävę ŧĥę đäşĥþőäřđ ƒįřşŧ.", From 5dea949433349a345985354f18846cfba4b6e2bf Mon Sep 17 00:00:00 2001 From: Nathan Marrs Date: Tue, 23 Apr 2024 15:26:53 -0600 Subject: [PATCH 069/222] Canvas: Match connection anchor points to elements (#85421) Co-authored-by: Ihor Yeromin --- public/app/features/canvas/element.ts | 6 +++ public/app/features/canvas/elements/cloud.tsx | 18 +++++++ .../app/features/canvas/elements/ellipse.tsx | 18 +++++++ .../canvas/elements/parallelogram.tsx | 19 +++++++ .../app/features/canvas/elements/triangle.tsx | 23 ++++++++ .../connections/ConnectionAnchors.tsx | 54 +++++++++---------- .../components/connections/Connections.tsx | 39 +++++++++++++- 7 files changed, 148 insertions(+), 29 deletions(-) diff --git a/public/app/features/canvas/element.ts b/public/app/features/canvas/element.ts index 371cc46ec43..61d7061eaaf 100644 --- a/public/app/features/canvas/element.ts +++ b/public/app/features/canvas/element.ts @@ -101,6 +101,12 @@ export interface CanvasElementItem extends RegistryI /** Optional config to customize what standard element editor options are available for the item */ standardEditorConfig?: StandardEditorConfig; + + /** Custom connection anchor coordinates, like for svg elements such as triangle, cloud, etc */ + customConnectionAnchors?: Array<{ + x: number; + y: number; + }>; } export const defaultBgColor = '#D9D9D9'; diff --git a/public/app/features/canvas/elements/cloud.tsx b/public/app/features/canvas/elements/cloud.tsx index 4f1bd04648c..1d48f43a35c 100644 --- a/public/app/features/canvas/elements/cloud.tsx +++ b/public/app/features/canvas/elements/cloud.tsx @@ -184,6 +184,24 @@ export const cloudItem: CanvasElementItem = { }, }); }, + + customConnectionAnchors: [ + { x: -0.58, y: 0.63 }, // Top Left + { x: -0.22, y: 0.99 }, // Top Middle + { x: 0.235, y: 0.75 }, // Top Right + + { x: 0.8, y: 0.6 }, // Right Top + { x: 0.785, y: 0.06 }, // Right Middle + { x: 0.91, y: -0.51 }, // Right Bottom + + { x: 0.62, y: -0.635 }, // Bottom Right + { x: 0.05, y: -0.98 }, // Bottom Middle + { x: -0.45, y: -0.635 }, // Bottom Left + + { x: -0.8, y: -0.58 }, // Left Bottom + { x: -0.78, y: -0.06 }, // Left Middle + { x: -0.9, y: 0.48 }, // Left Top + ], }; const getStyles = (theme: GrafanaTheme2, data: CanvasElementData | undefined) => { diff --git a/public/app/features/canvas/elements/ellipse.tsx b/public/app/features/canvas/elements/ellipse.tsx index 8cab935e0ec..b15b5908610 100644 --- a/public/app/features/canvas/elements/ellipse.tsx +++ b/public/app/features/canvas/elements/ellipse.tsx @@ -190,6 +190,24 @@ export const ellipseItem: CanvasElementItem { diff --git a/public/app/features/canvas/elements/parallelogram.tsx b/public/app/features/canvas/elements/parallelogram.tsx index 4caec1e776a..61c4b8531fa 100644 --- a/public/app/features/canvas/elements/parallelogram.tsx +++ b/public/app/features/canvas/elements/parallelogram.tsx @@ -184,6 +184,25 @@ export const parallelogramItem: CanvasElementItem = { }, }); }, + + customConnectionAnchors: [ + { x: -0.6, y: 1 }, // Angled Top Left + { x: -0.1, y: 1 }, // Top Middle + { x: 0.5, y: 1 }, // Angled Top Right + { x: 1, y: 1 }, // Top Right + { x: 0.925, y: 0.6 }, // Angled Right Top + { x: 0.84, y: 0.2 }, // Right Middle + { x: 0.76, y: -0.2 }, // Angled Right Bottom + { x: 0.675, y: -0.6 }, // Bottom Right + { x: -0.5, y: -1 }, // Angled Bottom Right + { x: 0.1, y: -1 }, // Bottom Middle + { x: 0.6, y: -1 }, // Angled Bottom Left + { x: -1, y: -1 }, // Bottom Left + { x: -0.925, y: -0.6 }, // Angled Left Bottom + { x: -0.84, y: -0.2 }, // Left Middle + { x: -0.76, y: 0.2 }, // Angled Left Top + { x: -0.675, y: 0.6 }, // Top Left 2 + ], }; const getStyles = (theme: GrafanaTheme2, data: CanvasElementData | undefined) => { diff --git a/public/app/features/canvas/elements/triangle.tsx b/public/app/features/canvas/elements/triangle.tsx index 6af74d68775..9a46e7f3dd5 100644 --- a/public/app/features/canvas/elements/triangle.tsx +++ b/public/app/features/canvas/elements/triangle.tsx @@ -185,6 +185,29 @@ export const triangleItem: CanvasElementItem = { }, }); }, + + customConnectionAnchors: [ + // points along the left edge + { x: -1, y: -1 }, // bottom left + { x: -0.8, y: -0.6 }, + { x: -0.6, y: -0.2 }, + { x: -0.4, y: 0.2 }, + { x: -0.2, y: 0.6 }, + { x: 0, y: 1 }, // top + + // points along the right edge + { x: 0.2, y: 0.6 }, + { x: 0.4, y: 0.2 }, + { x: 0.6, y: -0.2 }, + { x: 0.8, y: -0.6 }, + { x: 1, y: -1 }, // bottom right + + // points along the bottom edge + { x: 0.6, y: -1 }, + { x: 0.2, y: -1 }, + { x: -0.2, y: -1 }, + { x: -0.6, y: -1 }, + ], }; const getStyles = (theme: GrafanaTheme2, data: CanvasElementData | undefined) => { diff --git a/public/app/plugins/panel/canvas/components/connections/ConnectionAnchors.tsx b/public/app/plugins/panel/canvas/components/connections/ConnectionAnchors.tsx index cbd13b41e24..ed9903b30c0 100644 --- a/public/app/plugins/panel/canvas/components/connections/ConnectionAnchors.tsx +++ b/public/app/plugins/panel/canvas/components/connections/ConnectionAnchors.tsx @@ -7,6 +7,7 @@ import { ConnectionCoordinates } from 'app/features/canvas'; type Props = { setRef: (anchorElement: HTMLDivElement) => void; + setAnchorsRef: (anchorsElement: HTMLDivElement) => void; handleMouseLeave: ( event: React.MouseEvent | React.FocusEvent ) => boolean; @@ -15,13 +16,33 @@ type Props = { export const CONNECTION_ANCHOR_DIV_ID = 'connectionControl'; export const CONNECTION_ANCHOR_ALT = 'connection anchor'; export const CONNECTION_ANCHOR_HIGHLIGHT_OFFSET = 8; +// Unit is percentage from the middle of the element +// 0, 0 middle; -1, -1 bottom left; 1, 1 top right +export const ANCHORS = [ + { x: -1, y: 1 }, + { x: -0.5, y: 1 }, + { x: 0, y: 1 }, + { x: 0.5, y: 1 }, + { x: 1, y: 1 }, + { x: 1, y: 0.5 }, + { x: 1, y: 0 }, + { x: 1, y: -0.5 }, + { x: 1, y: -1 }, + { x: 0.5, y: -1 }, + { x: 0, y: -1 }, + { x: -0.5, y: -1 }, + { x: -1, y: -1 }, + { x: -1, y: -0.5 }, + { x: -1, y: 0 }, + { x: -1, y: 0.5 }, +]; -const ANCHOR_PADDING = 3; +export const ANCHOR_PADDING = 3; +export const HALF_SIZE = 2.5; -export const ConnectionAnchors = ({ setRef, handleMouseLeave }: Props) => { +export const ConnectionAnchors = ({ setRef, setAnchorsRef, handleMouseLeave }: Props) => { const highlightEllipseRef = useRef(null); const styles = useStyles2(getStyles); - const halfSize = 2.5; const halfSizeHighlightEllipse = 5.5; const anchorImage = 'data:image/svg+xml;base64,PCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI1cHgiIGhlaWdodD0iNXB4IiB2ZXJzaW9uPSIxLjEiPjxwYXRoIGQ9Im0gMCAwIEwgNSA1IE0gMCA1IEwgNSAwIiBzdHJva2Utd2lkdGg9IjIiIHN0eWxlPSJzdHJva2Utb3BhY2l0eTowLjQiIHN0cm9rZT0iI2ZmZmZmZiIvPjxwYXRoIGQ9Im0gMCAwIEwgNSA1IE0gMCA1IEwgNSAwIiBzdHJva2U9IiMyOWI2ZjIiLz48L3N2Zz4='; @@ -54,35 +75,14 @@ export const ConnectionAnchors = ({ setRef, handleMouseLeave }: Props) => { } }; - // Unit is percentage from the middle of the element - // 0, 0 middle; -1, -1 bottom left; 1, 1 top right - const ANCHORS = [ - { x: -1, y: 1 }, - { x: -0.5, y: 1 }, - { x: 0, y: 1 }, - { x: 0.5, y: 1 }, - { x: 1, y: 1 }, - { x: 1, y: 0.5 }, - { x: 1, y: 0 }, - { x: 1, y: -0.5 }, - { x: 1, y: -1 }, - { x: 0.5, y: -1 }, - { x: 0, y: -1 }, - { x: -0.5, y: -1 }, - { x: -1, y: -1 }, - { x: -1, y: -0.5 }, - { x: -1, y: 0 }, - { x: -1, y: 0.5 }, - ]; - const generateAnchors = (anchors: ConnectionCoordinates[] = ANCHORS) => { return anchors.map((anchor) => { const id = `${anchor.x},${anchor.y}`; // Convert anchor coords to relative percentage const style = { - top: `calc(${-anchor.y * 50 + 50}% - ${halfSize}px - ${ANCHOR_PADDING}px)`, - left: `calc(${anchor.x * 50 + 50}% - ${halfSize}px - ${ANCHOR_PADDING}px)`, + top: `calc(${-anchor.y * 50 + 50}% - ${HALF_SIZE}px - ${ANCHOR_PADDING}px)`, + left: `calc(${anchor.x * 50 + 50}% - ${HALF_SIZE}px - ${ANCHOR_PADDING}px)`, }; return ( @@ -114,7 +114,7 @@ export const ConnectionAnchors = ({ setRef, handleMouseLeave }: Props) => { className={styles.highlightElement} onMouseLeave={onMouseLeaveHighlightElement} /> - {generateAnchors()} +
{generateAnchors()}
); }; diff --git a/public/app/plugins/panel/canvas/components/connections/Connections.tsx b/public/app/plugins/panel/canvas/components/connections/Connections.tsx index b0e794ff8c1..2c066bcea63 100644 --- a/public/app/plugins/panel/canvas/components/connections/Connections.tsx +++ b/public/app/plugins/panel/canvas/components/connections/Connections.tsx @@ -16,7 +16,14 @@ import { isConnectionTarget, } from '../../utils'; -import { CONNECTION_ANCHOR_ALT, ConnectionAnchors, CONNECTION_ANCHOR_HIGHLIGHT_OFFSET } from './ConnectionAnchors'; +import { + CONNECTION_ANCHOR_ALT, + ConnectionAnchors, + CONNECTION_ANCHOR_HIGHLIGHT_OFFSET, + ANCHORS, + ANCHOR_PADDING, + HALF_SIZE, +} from './ConnectionAnchors'; import { ConnectionSVG } from './ConnectionSVG'; export const CONNECTION_VERTEX_ID = 'vertex'; @@ -27,6 +34,7 @@ const CONNECTION_VERTEX_SNAP_TOLERANCE = (5 / 180) * Math.PI; // Multi-segment s export class Connections { scene: Scene; connectionAnchorDiv?: HTMLDivElement; + anchorsDiv?: HTMLDivElement; connectionSVG?: SVGElement; connectionLine?: SVGLineElement; connectionSVGVertex?: SVGElement; @@ -70,6 +78,10 @@ export class Connections { this.connectionAnchorDiv = anchorElement; }; + setAnchorsRef = (anchorsElement: HTMLDivElement) => { + this.anchorsDiv = anchorsElement; + }; + setConnectionSVGRef = (connectionSVG: SVGSVGElement) => { this.connectionSVG = connectionSVG; }; @@ -130,6 +142,25 @@ export class Connections { } } + const customElementAnchors = element?.item.customConnectionAnchors || ANCHORS; + // This type cast is necessary as TS doesn't understand that `Element` is an `HTMLElement` + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const anchors = Array.from(this.anchorsDiv?.children as HTMLCollectionOf); + const anchorsAmount = customElementAnchors.length; + + // re-calculate the position of the existing anchors on hover + // and hide the rest of the anchors if there are more than the custom ones + anchors.forEach((anchor, index) => { + if (index >= anchorsAmount) { + anchor.style.display = 'none'; + } else { + const { x, y } = customElementAnchors[index]; + anchor.style.top = `calc(${-y * 50 + 50}% - ${HALF_SIZE}px - ${ANCHOR_PADDING}px)`; + anchor.style.left = `calc(${x * 50 + 50}% - ${HALF_SIZE}px - ${ANCHOR_PADDING}px)`; + anchor.style.display = 'block'; + } + }); + const elementBoundingRect = element.div!.getBoundingClientRect(); const transformScale = this.scene.scale; const parentBoundingRect = getParentBoundingClientRect(this.scene); @@ -648,7 +679,11 @@ export class Connections { render() { return ( <> - + Date: Tue, 23 Apr 2024 23:28:37 +0100 Subject: [PATCH 070/222] Chore: Rewrite grafana-flamegraph css using object styles (#86816) --- .betterer.results | 40 ----- .../src/FlameGraph/FlameGraph.tsx | 53 +++---- .../src/FlameGraph/FlameGraphMetadata.tsx | 59 ++++--- .../src/FlameGraph/FlameGraphTooltip.tsx | 52 +++--- .../src/FlameGraphHeader.tsx | 150 +++++++++--------- .../TopTable/FlameGraphTopTableContainer.tsx | 21 ++- 6 files changed, 165 insertions(+), 210 deletions(-) diff --git a/.betterer.results b/.betterer.results index cdf32535173..d12c0a98415 100644 --- a/.betterer.results +++ b/.betterer.results @@ -454,46 +454,6 @@ exports[`better eslint`] = { "packages/grafana-data/test/__mocks__/pluginMocks.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "packages/grafana-flamegraph/src/FlameGraph/FlameGraph.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"] - ], - "packages/grafana-flamegraph/src/FlameGraph/FlameGraphMetadata.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"] - ], - "packages/grafana-flamegraph/src/FlameGraph/FlameGraphTooltip.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"] - ], - "packages/grafana-flamegraph/src/FlameGraphHeader.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"], - [0, 0, 0, "Styles should be written using objects.", "8"], - [0, 0, 0, "Styles should be written using objects.", "9"], - [0, 0, 0, "Styles should be written using objects.", "10"], - [0, 0, 0, "Styles should be written using objects.", "11"], - [0, 0, 0, "Styles should be written using objects.", "12"] - ], - "packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], "packages/grafana-o11y-ds-frontend/src/utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], diff --git a/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.tsx b/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.tsx index 0a207cb94ea..5340bd9d122 100644 --- a/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.tsx +++ b/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.tsx @@ -185,33 +185,32 @@ const FlameGraph = ({ }; const getStyles = () => ({ - graph: css` - label: graph; - overflow: auto; - flex-grow: 1; - flex-basis: 50%; - `, - sandwichCanvasWrapper: css` - label: sandwichCanvasWrapper; - display: flex; - margin-bottom: ${PIXELS_PER_LEVEL / window.devicePixelRatio}px; - `, - sandwichMarker: css` - label: sandwichMarker; - writing-mode: vertical-lr; - transform: rotate(180deg); - overflow: hidden; - white-space: nowrap; - `, - - sandwichMarkerCalees: css` - label: sandwichMarkerCalees; - text-align: right; - `, - sandwichMarkerIcon: css` - label: sandwichMarkerIcon; - vertical-align: baseline; - `, + graph: css({ + label: 'graph', + overflow: 'auto', + flexGrow: 1, + flexBasis: '50%', + }), + sandwichCanvasWrapper: css({ + label: 'sandwichCanvasWrapper', + display: 'flex', + marginBottom: `${PIXELS_PER_LEVEL / window.devicePixelRatio}px`, + }), + sandwichMarker: css({ + label: 'sandwichMarker', + writingMode: 'vertical-lr', + transform: 'rotate(180deg)', + overflow: 'hidden', + whiteSpace: 'nowrap', + }), + sandwichMarkerCalees: css({ + label: 'sandwichMarkerCalees', + textAlign: 'right', + }), + sandwichMarkerIcon: css({ + label: 'sandwichMarkerIcon', + verticalAlign: 'baseline', + }), }); export default FlameGraph; diff --git a/packages/grafana-flamegraph/src/FlameGraph/FlameGraphMetadata.tsx b/packages/grafana-flamegraph/src/FlameGraph/FlameGraphMetadata.tsx index 9a896f33a0d..d385b7dd148 100644 --- a/packages/grafana-flamegraph/src/FlameGraph/FlameGraphMetadata.tsx +++ b/packages/grafana-flamegraph/src/FlameGraph/FlameGraphMetadata.tsx @@ -89,36 +89,35 @@ const FlameGraphMetadata = React.memo( FlameGraphMetadata.displayName = 'FlameGraphMetadata'; const getStyles = (theme: GrafanaTheme2) => ({ - metadataPill: css` - label: metadataPill; - display: inline-flex; - align-items: center; - background: ${theme.colors.background.secondary}; - border-radius: ${theme.shape.borderRadius(8)}; - padding: ${theme.spacing(0.5, 1)}; - font-size: ${theme.typography.bodySmall.fontSize}; - font-weight: ${theme.typography.fontWeightMedium}; - line-height: ${theme.typography.bodySmall.lineHeight}; - color: ${theme.colors.text.secondary}; - `, - - pillCloseButton: css` - label: pillCloseButton; - vertical-align: text-bottom; - margin: ${theme.spacing(0, 0.5)}; - `, - metadata: css` - margin: 8px 0; - text-align: center; - `, - metadataPillName: css` - label: metadataPillName; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: ${theme.spacing(0.5)}; - `, + metadataPill: css({ + label: 'metadataPill', + display: 'inline-flex', + alignItems: 'center', + background: theme.colors.background.secondary, + borderRadius: theme.shape.borderRadius(8), + padding: theme.spacing(0.5, 1), + fontSize: theme.typography.bodySmall.fontSize, + fontWeight: theme.typography.fontWeightMedium, + lineHeight: theme.typography.bodySmall.lineHeight, + color: theme.colors.text.secondary, + }), + pillCloseButton: css({ + label: 'pillCloseButton', + verticalAlign: 'text-bottom', + margin: theme.spacing(0, 0.5), + }), + metadata: css({ + margin: '8px 0', + textAlign: 'center', + }), + metadataPillName: css({ + label: 'metadataPillName', + maxWidth: '200px', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + marginLeft: theme.spacing(0.5), + }), }); export default FlameGraphMetadata; diff --git a/packages/grafana-flamegraph/src/FlameGraph/FlameGraphTooltip.tsx b/packages/grafana-flamegraph/src/FlameGraph/FlameGraphTooltip.tsx index d2fe2866606..62f4a53343a 100644 --- a/packages/grafana-flamegraph/src/FlameGraph/FlameGraphTooltip.tsx +++ b/packages/grafana-flamegraph/src/FlameGraph/FlameGraphTooltip.tsx @@ -182,33 +182,33 @@ function getValueWithUnit(data: FlameGraphDataContainer, displayValue: DisplayVa } const getStyles = (theme: GrafanaTheme2) => ({ - tooltipContainer: css` - title: tooltipContainer; - overflow: hidden; - `, - tooltipContent: css` - title: tooltipContent; - font-size: ${theme.typography.bodySmall.fontSize}; - width: 100%; - `, - tooltipName: css` - title: tooltipName; - margin-top: 0; - word-break: break-all; - `, - lastParagraph: css` - title: lastParagraph; - margin-bottom: 0; - `, - name: css` - title: name; - margin-bottom: 10px; - `, + tooltipContainer: css({ + title: 'tooltipContainer', + overflow: 'hidden', + }), + tooltipContent: css({ + title: 'tooltipContent', + fontSize: theme.typography.bodySmall.fontSize, + width: '100%', + }), + tooltipName: css({ + title: 'tooltipName', + marginTop: 0, + wordBreak: 'break-all', + }), + lastParagraph: css({ + title: 'lastParagraph', + marginBottom: 0, + }), + name: css({ + title: 'name', + marginBottom: '10px', + }), - tooltipTable: css` - title: tooltipTable; - max-width: 400px; - `, + tooltipTable: css({ + title: 'tooltipTable', + maxWidth: '400px', + }), }); export default FlameGraphTooltip; diff --git a/packages/grafana-flamegraph/src/FlameGraphHeader.tsx b/packages/grafana-flamegraph/src/FlameGraphHeader.tsx index 1e372a93660..20606e0637c 100644 --- a/packages/grafana-flamegraph/src/FlameGraphHeader.tsx +++ b/packages/grafana-flamegraph/src/FlameGraphHeader.tsx @@ -46,7 +46,7 @@ const FlameGraphHeader = ({ vertical, isDiffMode, }: Props) => { - const styles = useStyles2(getStyles, stickyHeader); + const styles = useStyles2(getStyles); const [localSearch, setLocalSearch] = useSearchInput(search, setSearch); const suffix = @@ -66,7 +66,7 @@ const FlameGraphHeader = ({ ) : null; return ( -
+
props.onChange(ColorScheme.PackageBased)} /> @@ -213,79 +213,77 @@ function useSearchInput( return [localSearchState, setLocalSearchState]; } -const getStyles = (theme: GrafanaTheme2, sticky?: boolean) => ({ - header: css` - label: header; - display: flex; - flex-wrap: wrap; - justify-content: space-between; - width: 100%; - top: 0; - ${sticky - ? css` - z-index: ${theme.zIndex.navbarFixed}; - position: sticky; - padding-bottom: ${theme.spacing(1)}; - padding-top: ${theme.spacing(1)}; - background: ${theme.colors.background.primary}; - ` - : ''}; - `, - inputContainer: css` - label: inputContainer; - margin-right: 20px; - flex-grow: 1; - min-width: 150px; - max-width: 350px; - `, - rightContainer: css` - label: rightContainer; - display: flex; - align-items: flex-start; - flex-wrap: wrap; - `, - buttonSpacing: css` - label: buttonSpacing; - margin-right: ${theme.spacing(1)}; - `, - - resetButton: css` - label: resetButton; - display: flex; - margin-right: ${theme.spacing(2)}; - `, - resetButtonIconWrapper: css` - label: resetButtonIcon; - padding: 0 5px; - color: ${theme.colors.text.disabled}; - `, - colorDot: css` - label: colorDot; - display: inline-block; - width: 10px; - height: 10px; - border-radius: 50%; - `, - colorDotByValue: css` - label: colorDotByValue; - background: ${byValueGradient}; - `, - colorDotByPackage: css` - label: colorDotByPackage; - background: ${byPackageGradient}; - `, - colorDotDiffDefault: css` - label: colorDotDiffDefault; - background: ${diffDefaultGradient}; - `, - colorDotDiffColorBlind: css` - label: colorDotDiffColorBlind; - background: ${diffColorBlindGradient}; - `, - extraElements: css` - label: extraElements; - margin-left: ${theme.spacing(1)}; - `, +const getStyles = (theme: GrafanaTheme2) => ({ + header: css({ + label: 'header', + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'space-between', + width: '100%', + top: 0, + }), + stickyHeader: css({ + zIndex: theme.zIndex.navbarFixed, + position: 'sticky', + paddingBottom: theme.spacing(1), + paddingTop: theme.spacing(1), + background: theme.colors.background.primary, + }), + inputContainer: css({ + label: 'inputContainer', + marginRight: '20px', + flexGrow: 1, + minWidth: '150px', + maxWidth: '350px', + }), + rightContainer: css({ + label: 'rightContainer', + display: 'flex', + alignItems: 'flex-start', + flexWrap: 'wrap', + }), + buttonSpacing: css({ + label: 'buttonSpacing', + marginRight: theme.spacing(1), + }), + resetButton: css({ + label: 'resetButton', + display: 'flex', + marginRight: theme.spacing(2), + }), + resetButtonIconWrapper: css({ + label: 'resetButtonIcon', + padding: '0 5px', + color: theme.colors.text.disabled, + }), + colorDot: css({ + label: 'colorDot', + display: 'inline-block', + width: '10px', + height: '10px', + // eslint-disable-next-line @grafana/no-border-radius-literal + borderRadius: '50%', + }), + colorDotByValue: css({ + label: 'colorDotByValue', + background: byValueGradient, + }), + colorDotByPackage: css({ + label: 'colorDotByPackage', + background: byPackageGradient, + }), + colorDotDiffDefault: css({ + label: 'colorDotDiffDefault', + background: diffDefaultGradient, + }), + colorDotDiffColorBlind: css({ + label: 'colorDotDiffColorBlind', + background: diffColorBlindGradient, + }), + extraElements: css({ + label: 'extraElements', + marginLeft: theme.spacing(1), + }), }); export default FlameGraphHeader; diff --git a/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx b/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx index 936be013806..2630e7dcf9d 100644 --- a/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx +++ b/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx @@ -332,17 +332,16 @@ const getStyles = (theme: GrafanaTheme2) => { const getStylesActionCell = () => { return { - actionCellWrapper: css` - label: actionCellWrapper; - display: flex; - height: 24px; - `, - - actionCellButton: css` - label: actionCellButton; - margin-right: 0; - width: 24px; - `, + actionCellWrapper: css({ + label: 'actionCellWrapper', + display: 'flex', + height: '24px', + }), + actionCellButton: css({ + label: 'actionCellButton', + marginRight: 0, + width: '24px', + }), }; }; From 309adcaa7f6803bd9de2e03948df057ebac33c10 Mon Sep 17 00:00:00 2001 From: nextMJ <77353838+nextMJ@users.noreply.github.com> Date: Wed, 24 Apr 2024 07:13:51 +0200 Subject: [PATCH 071/222] Typo fix in User API doc example (#81890) Co-authored-by: Jack Baldry --- docs/sources/developers/http_api/user.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/developers/http_api/user.md b/docs/sources/developers/http_api/user.md index 1443f72b1b4..37c64bffa24 100644 --- a/docs/sources/developers/http_api/user.md +++ b/docs/sources/developers/http_api/user.md @@ -66,7 +66,7 @@ Content-Type: application/json "isAdmin": true, "isDisabled": false, "lastSeenAt": "2020-04-10T20:29:27+03:00", - "lastSeenAtAge': "2m", + "lastSeenAtAge": "2m", "authLabels": ["OAuth"] }, { From d48b5ea44ddaaf93288f2ec377c7a23adbfecf8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Boudereau?= Date: Wed, 24 Apr 2024 08:42:29 +0200 Subject: [PATCH 072/222] Docs: Fix data source (jaeger, zipkin, tempo) provisioning yaml TracesToLog query example (#86606) --- docs/sources/datasources/jaeger/_index.md | 2 +- docs/sources/datasources/tempo/configure-tempo-data-source.md | 4 ++-- docs/sources/datasources/zipkin/_index.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/datasources/jaeger/_index.md b/docs/sources/datasources/jaeger/_index.md index 36dc7d0131e..55e18194421 100644 --- a/docs/sources/datasources/jaeger/_index.md +++ b/docs/sources/datasources/jaeger/_index.md @@ -194,7 +194,7 @@ datasources: filterByTraceID: false filterBySpanID: false customQuery: true - query: 'method="${__span.tags.method}"' + query: 'method="$${__span.tags.method}"' tracesToMetrics: datasourceUid: 'prom' spanStartTimeShift: '1h' diff --git a/docs/sources/datasources/tempo/configure-tempo-data-source.md b/docs/sources/datasources/tempo/configure-tempo-data-source.md index ffde7d5ffe8..ab34f5875c2 100644 --- a/docs/sources/datasources/tempo/configure-tempo-data-source.md +++ b/docs/sources/datasources/tempo/configure-tempo-data-source.md @@ -238,7 +238,7 @@ datasources: filterByTraceID: false filterBySpanID: false customQuery: true - query: 'method="${__span.tags.method}"' + query: 'method="$${__span.tags.method}"' tracesToMetrics: datasourceUid: 'prom' spanStartTimeShift: '1h' @@ -252,7 +252,7 @@ datasources: tags: ['job', 'instance', 'pod', 'namespace'] profileTypeId: 'process_cpu:cpu:nanoseconds:cpu:nanoseconds' customQuery: true - query: 'method="${__span.tags.method}"' + query: 'method="$${__span.tags.method}"' serviceMap: datasourceUid: 'prometheus' nodeGraph: diff --git a/docs/sources/datasources/zipkin/_index.md b/docs/sources/datasources/zipkin/_index.md index b12037fbe2c..50c749c3d35 100644 --- a/docs/sources/datasources/zipkin/_index.md +++ b/docs/sources/datasources/zipkin/_index.md @@ -192,7 +192,7 @@ datasources: filterByTraceID: false filterBySpanID: false customQuery: true - query: 'method="${__span.tags.method}"' + query: 'method="$${__span.tags.method}"' tracesToMetrics: datasourceUid: 'prom' spanStartTimeShift: '1h' From 804c72641337347403c878fff24f9fdd10f2c637 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Wed, 24 Apr 2024 09:33:16 +0200 Subject: [PATCH 073/222] PluginExtensions: Make the extensions registry reactive (#83085) * feat: add a reactive extension registry Co-authored-by: Marcus Andersson * feat: add hooks to work with the reactive registry Co-authored-by: Marcus Andersson * feat: start using the reactive registry Co-authored-by: Marcus Andersson * feat: update the "command palette" extension point to use the hook * feat: update the "alerting" extension point to use the hooks Co-authored-by: Marcus Andersson * feat: update the "explore" extension point to use the hooks Co-authored-by: Marcus Andersson * feat: update the "datasources config" extension point to use the hooks Co-authored-by: Marcus Andersson * feat: update the "panel menu" extension point to use the hooks Co-authored-by: Marcus Andersson * feat: update the "pyroscope datasource" extension point to use the hooks Co-authored-by: Marcus Andersson * feat: update the "user profile page" extension point to use the hooks * chore: update betterer * fix: update the hooks to not re-render unnecessarily * chore: remove the old `createPluginExtensionRegistry` impementation * chore: add "TODO" for `PanelMenuBehaviour` extension point * feat: update the return value of the hooks to contain a `{ isLoading }` param * tests: add more tests for the usePluginExtensions() hook * fix: exclude the cloud-home-app from being non-awaited * refactor: use uuidv4() for random ID generation (for the registry object) * fix: linting issue * feat: use the hooks for the new alerting extension point * feat: use `useMemo()` for `AlertInstanceAction` extension point context --------- Co-authored-by: Levente Balogh --- .betterer.results | 6 + .../grafana-runtime/src/services/index.ts | 10 + .../pluginExtensions/getPluginExtensions.ts | 23 +- .../usePluginExtensions.test.tsx | 262 +++++++ .../pluginExtensions/usePluginExtensions.ts | 50 ++ public/app/app.ts | 39 +- .../components/AppChrome/AppChrome.test.tsx | 2 +- .../alerting/unified/RuleList.test.tsx | 6 +- .../AlertInstanceExtensionPoint.tsx | 21 +- .../components/rules/RuleDetails.test.tsx | 9 +- .../RuleDetailsMatchingInstances.test.tsx | 8 +- .../unified/home/PluginIntegrations.tsx | 4 +- .../actions/extensionActions.ts | 22 - .../commandPalette/actions/staticActions.ts | 5 +- .../commandPalette/actions/useActions.ts | 6 +- .../actions/useExtensionActions.ts | 29 + .../scene/PanelMenuBehavior.tsx | 2 + .../containers/DashboardPage.test.tsx | 1 + .../PanelHeader/PanelHeaderMenuProvider.tsx | 41 +- .../dashboard/utils/getPanelMenu.test.ts | 404 +++-------- .../features/dashboard/utils/getPanelMenu.ts | 37 +- .../components/EditDataSource.test.tsx | 10 +- .../datasources/components/EditDataSource.tsx | 18 +- public/app/features/explore/Explore.test.tsx | 9 +- .../extensions/ToolbarExtensionPoint.test.tsx | 26 +- .../extensions/ToolbarExtensionPoint.tsx | 20 +- .../features/explore/spec/helper/setup.tsx | 4 +- .../createPluginExtensionRegistry.test.ts | 140 ---- .../createPluginExtensionRegistry.ts | 39 - .../extensions/getPluginExtensions.test.tsx | 99 +-- .../plugins/extensions/getPluginExtensions.ts | 17 +- .../reactivePluginExtensionRegistry.test.ts | 682 ++++++++++++++++++ .../reactivePluginExtensionRegistry.ts | 79 ++ .../app/features/plugins/extensions/types.ts | 5 +- .../extensions/usePluginExtensions.test.tsx | 225 ++++++ .../extensions/usePluginExtensions.tsx | 54 ++ .../app/features/plugins/pluginPreloader.ts | 13 +- .../profile/UserProfileEditPage.test.tsx | 8 +- .../features/profile/UserProfileEditPage.tsx | 35 +- .../QueryEditor/QueryEditor.test.tsx | 4 +- .../QueryEditor/QueryLinkExtension.test.tsx | 13 +- .../QueryEditor/QueryLinkExtension.tsx | 4 +- .../datasource.test.ts | 4 +- .../panel/alertlist/UnifiedalertList.test.tsx | 9 +- 44 files changed, 1768 insertions(+), 736 deletions(-) create mode 100644 packages/grafana-runtime/src/services/pluginExtensions/usePluginExtensions.test.tsx create mode 100644 packages/grafana-runtime/src/services/pluginExtensions/usePluginExtensions.ts delete mode 100644 public/app/features/commandPalette/actions/extensionActions.ts create mode 100644 public/app/features/commandPalette/actions/useExtensionActions.ts delete mode 100644 public/app/features/plugins/extensions/createPluginExtensionRegistry.test.ts delete mode 100644 public/app/features/plugins/extensions/createPluginExtensionRegistry.ts create mode 100644 public/app/features/plugins/extensions/reactivePluginExtensionRegistry.test.ts create mode 100644 public/app/features/plugins/extensions/reactivePluginExtensionRegistry.ts create mode 100644 public/app/features/plugins/extensions/usePluginExtensions.test.tsx create mode 100644 public/app/features/plugins/extensions/usePluginExtensions.tsx diff --git a/.betterer.results b/.betterer.results index d12c0a98415..b1db9758f60 100644 --- a/.betterer.results +++ b/.betterer.results @@ -700,6 +700,9 @@ exports[`better eslint`] = { "packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], + "packages/grafana-runtime/src/services/pluginExtensions/usePluginExtensions.ts:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"] + ], "packages/grafana-runtime/src/utils/DataSourceWithBackend.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] @@ -4072,6 +4075,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "11"], [0, 0, 0, "Do not use any type assertions.", "12"] ], + "public/app/features/plugins/extensions/getPluginExtensions.test.tsx:5381": [ + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] + ], "public/app/features/plugins/loader/sharedDependencies.ts:5381": [ [0, 0, 0, "* import is invalid because \'Layout,HorizontalGroup,VerticalGroup\' from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], diff --git a/packages/grafana-runtime/src/services/index.ts b/packages/grafana-runtime/src/services/index.ts index 56208d451f8..f60715cbe5d 100644 --- a/packages/grafana-runtime/src/services/index.ts +++ b/packages/grafana-runtime/src/services/index.ts @@ -15,5 +15,15 @@ export { getPluginLinkExtensions, getPluginComponentExtensions, type GetPluginExtensions, + type GetPluginExtensionsOptions, + type GetPluginExtensionsResult, + type UsePluginExtensions, + type UsePluginExtensionsResult, } from './pluginExtensions/getPluginExtensions'; +export { + setPluginExtensionsHook, + usePluginExtensions, + usePluginLinkExtensions, + usePluginComponentExtensions, +} from './pluginExtensions/usePluginExtensions'; export { isPluginExtensionLink, isPluginExtensionComponent } from './pluginExtensions/utils'; diff --git a/packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts b/packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts index dcd52b200b2..eff1975e8f4 100644 --- a/packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts +++ b/packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts @@ -2,18 +2,29 @@ import type { PluginExtension, PluginExtensionLink, PluginExtensionComponent } f import { isPluginExtensionComponent, isPluginExtensionLink } from './utils'; -export type GetPluginExtensions = ({ - extensionPointId, - context, - limitPerPlugin, -}: { +export type GetPluginExtensions = ( + options: GetPluginExtensionsOptions +) => GetPluginExtensionsResult; + +export type UsePluginExtensions = ( + options: GetPluginExtensionsOptions +) => UsePluginExtensionsResult; + +export type GetPluginExtensionsOptions = { extensionPointId: string; context?: object | Record; limitPerPlugin?: number; -}) => { +}; + +export type GetPluginExtensionsResult = { extensions: T[]; }; +export type UsePluginExtensionsResult = { + extensions: T[]; + isLoading: boolean; +}; + let singleton: GetPluginExtensions | undefined; export function setPluginExtensionGetter(instance: GetPluginExtensions): void { diff --git a/packages/grafana-runtime/src/services/pluginExtensions/usePluginExtensions.test.tsx b/packages/grafana-runtime/src/services/pluginExtensions/usePluginExtensions.test.tsx new file mode 100644 index 00000000000..de943427e64 --- /dev/null +++ b/packages/grafana-runtime/src/services/pluginExtensions/usePluginExtensions.test.tsx @@ -0,0 +1,262 @@ +import { renderHook } from '@testing-library/react-hooks'; + +import { PluginExtension, PluginExtensionTypes } from '@grafana/data'; + +import { UsePluginExtensions } from './getPluginExtensions'; +import { + setPluginExtensionsHook, + usePluginComponentExtensions, + usePluginExtensions, + usePluginLinkExtensions, +} from './usePluginExtensions'; + +describe('Plugin Extensions / usePluginExtensions', () => { + afterEach(() => { + process.env.NODE_ENV = 'test'; + }); + + test('should always return the same extension-hook function that was previously set', () => { + const hook: UsePluginExtensions = jest.fn().mockReturnValue({ extensions: [], isLoading: false }); + + setPluginExtensionsHook(hook); + usePluginExtensions({ extensionPointId: 'panel-menu' }); + + expect(hook).toHaveBeenCalledTimes(1); + expect(hook).toHaveBeenCalledWith({ extensionPointId: 'panel-menu' }); + }); + + test('should throw an error when trying to redefine the app-wide extension-hook function', () => { + // By default, NODE_ENV is set to 'test' in jest.config.js, which allows to override the registry in tests. + process.env.NODE_ENV = 'production'; + + const hook: UsePluginExtensions = () => ({ extensions: [], isLoading: false }); + + expect(() => { + setPluginExtensionsHook(hook); + setPluginExtensionsHook(hook); + }).toThrow(); + }); + + test('should throw an error when trying to access the extension-hook function before it was set', () => { + // "Unsetting" the registry + // @ts-ignore + setPluginExtensionsHook(undefined); + + expect(() => { + usePluginExtensions({ extensionPointId: 'panel-menu' }); + }).toThrow(); + }); + + describe('usePluginExtensionLinks()', () => { + test('should return only links extensions', () => { + const usePluginExtensionsMock: UsePluginExtensions = () => ({ + extensions: [ + { + id: '1', + pluginId: '', + title: '', + description: '', + type: PluginExtensionTypes.component, + component: () => undefined, + }, + { + id: '2', + pluginId: '', + title: '', + description: '', + path: '', + type: PluginExtensionTypes.link, + }, + { + id: '3', + pluginId: '', + title: '', + description: '', + path: '', + type: PluginExtensionTypes.link, + }, + ], + isLoading: false, + }); + + setPluginExtensionsHook(usePluginExtensionsMock); + + const { result } = renderHook(() => usePluginLinkExtensions({ extensionPointId: 'panel-menu' })); + const { extensions } = result.current; + + expect(extensions).toHaveLength(2); + expect(extensions[0].type).toBe('link'); + expect(extensions[1].type).toBe('link'); + expect(extensions.find(({ id }) => id === '2')).toBeDefined(); + expect(extensions.find(({ id }) => id === '3')).toBeDefined(); + }); + + test('should return the same object if the extensions do not change', () => { + const extensionPointId = 'foo'; + const extensions: PluginExtension[] = [ + { + id: '1', + pluginId: '', + title: '', + description: '', + path: '', + type: PluginExtensionTypes.link, + }, + ]; + + // Mimicing that the extensions do not change between renders + const usePluginExtensionsMock: UsePluginExtensions = () => ({ + extensions, + isLoading: false, + }); + + setPluginExtensionsHook(usePluginExtensionsMock); + + const { result, rerender } = renderHook(() => usePluginLinkExtensions({ extensionPointId })); + const firstExtensions = result.current.extensions; + + rerender(); + + const secondExtensions = result.current.extensions; + + expect(firstExtensions === secondExtensions).toBe(true); + }); + + test('should return a different object if the extensions do change', () => { + const extensionPointId = 'foo'; + + // Mimicing that the extensions is a new array object every time + const usePluginExtensionsMock: UsePluginExtensions = () => ({ + extensions: [ + { + id: '1', + pluginId: '', + title: '', + description: '', + path: '', + type: PluginExtensionTypes.link, + }, + ], + isLoading: false, + }); + + setPluginExtensionsHook(usePluginExtensionsMock); + + const { result, rerender } = renderHook(() => usePluginLinkExtensions({ extensionPointId })); + const firstExtensions = result.current.extensions; + + rerender(); + + const secondExtensions = result.current.extensions; + + // The results differ + expect(firstExtensions === secondExtensions).toBe(false); + }); + }); + + describe('usePluginExtensionComponents()', () => { + test('should return only component extensions', () => { + const hook: UsePluginExtensions = () => ({ + extensions: [ + { + id: '1', + pluginId: '', + title: '', + description: '', + type: PluginExtensionTypes.component, + component: () => undefined, + }, + { + id: '2', + pluginId: '', + title: '', + description: '', + path: '', + type: PluginExtensionTypes.link, + }, + { + id: '3', + pluginId: '', + title: '', + description: '', + path: '', + type: PluginExtensionTypes.link, + }, + ], + isLoading: false, + }); + + setPluginExtensionsHook(hook); + + const hookRender = renderHook(() => usePluginComponentExtensions({ extensionPointId: 'panel-menu' })); + const { extensions } = hookRender.result.current; + + expect(extensions).toHaveLength(1); + expect(extensions[0].type).toBe('component'); + expect(extensions.find(({ id }) => id === '1')).toBeDefined(); + }); + + test('should return the same object if the extensions do not change', () => { + const extensionPointId = 'foo'; + const extensions: PluginExtension[] = [ + { + id: '1', + pluginId: '', + title: '', + description: '', + type: PluginExtensionTypes.component, + component: () => undefined, + }, + ]; + + // Mimicing that the extensions do not change between renders + const usePluginExtensionsMock: UsePluginExtensions = () => ({ + extensions, + isLoading: false, + }); + + setPluginExtensionsHook(usePluginExtensionsMock); + + const { result, rerender } = renderHook(() => usePluginComponentExtensions({ extensionPointId })); + const firstExtensions = result.current.extensions; + + rerender(); + + const secondExtensions = result.current.extensions; + + // The results are the same + expect(firstExtensions === secondExtensions).toBe(true); + }); + + test('should return a different object if the extensions do change', () => { + const extensionPointId = 'foo'; + + // Mimicing that the extensions is a new array object every time + const usePluginExtensionsMock: UsePluginExtensions = () => ({ + extensions: [ + { + id: '1', + pluginId: '', + title: '', + description: '', + type: PluginExtensionTypes.component, + component: () => undefined, + }, + ], + isLoading: false, + }); + + setPluginExtensionsHook(usePluginExtensionsMock); + + const { result, rerender } = renderHook(() => usePluginComponentExtensions({ extensionPointId })); + const firstExtensions = result.current.extensions; + + rerender(); + + const secondExtensions = result.current.extensions; + + // The results differ + expect(firstExtensions === secondExtensions).toBe(false); + }); + }); +}); diff --git a/packages/grafana-runtime/src/services/pluginExtensions/usePluginExtensions.ts b/packages/grafana-runtime/src/services/pluginExtensions/usePluginExtensions.ts new file mode 100644 index 00000000000..4629fc7baab --- /dev/null +++ b/packages/grafana-runtime/src/services/pluginExtensions/usePluginExtensions.ts @@ -0,0 +1,50 @@ +import { useMemo } from 'react'; + +import { PluginExtensionComponent, PluginExtensionLink } from '@grafana/data'; + +import { GetPluginExtensionsOptions, UsePluginExtensions, UsePluginExtensionsResult } from './getPluginExtensions'; +import { isPluginExtensionComponent, isPluginExtensionLink } from './utils'; + +let singleton: UsePluginExtensions | undefined; + +export function setPluginExtensionsHook(hook: UsePluginExtensions): void { + // We allow overriding the registry in tests + if (singleton && process.env.NODE_ENV !== 'test') { + throw new Error('setPluginExtensionsHook() function should only be called once, when Grafana is starting.'); + } + singleton = hook; +} + +export function usePluginExtensions(options: GetPluginExtensionsOptions): UsePluginExtensionsResult { + if (!singleton) { + throw new Error('usePluginExtensions(options) can only be used after the Grafana instance has started.'); + } + return singleton(options); +} + +export function usePluginLinkExtensions( + options: GetPluginExtensionsOptions +): UsePluginExtensionsResult { + const { extensions, isLoading } = usePluginExtensions(options); + + return useMemo(() => { + return { + extensions: extensions.filter(isPluginExtensionLink), + isLoading, + }; + }, [extensions, isLoading]); +} + +export function usePluginComponentExtensions( + options: GetPluginExtensionsOptions +): { extensions: Array>; isLoading: boolean } { + const { extensions, isLoading } = usePluginExtensions(options); + + return useMemo( + () => ({ + extensions: extensions.filter(isPluginExtensionComponent) as Array>, + isLoading, + }), + [extensions, isLoading] + ); +} diff --git a/public/app/app.ts b/public/app/app.ts index 5796b18c21e..56fa4405843 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -36,7 +36,7 @@ import { setEmbeddedDashboard, setAppEvents, setReturnToPreviousHook, - type GetPluginExtensions, + setPluginExtensionsHook, } from '@grafana/runtime'; import { setPanelDataErrorView } from '@grafana/runtime/src/components/PanelDataErrorView'; import { setPanelRenderer } from '@grafana/runtime/src/components/PanelRenderer'; @@ -80,11 +80,12 @@ import { initGrafanaLive } from './features/live'; import { PanelDataErrorView } from './features/panel/components/PanelDataErrorView'; import { PanelRenderer } from './features/panel/components/PanelRenderer'; import { DatasourceSrv } from './features/plugins/datasource_srv'; -import { createPluginExtensionRegistry } from './features/plugins/extensions/createPluginExtensionRegistry'; import { getCoreExtensionConfigurations } from './features/plugins/extensions/getCoreExtensionConfigurations'; -import { getPluginExtensions } from './features/plugins/extensions/getPluginExtensions'; +import { createPluginExtensionsGetter } from './features/plugins/extensions/getPluginExtensions'; +import { ReactivePluginExtensionsRegistry } from './features/plugins/extensions/reactivePluginExtensionRegistry'; +import { createPluginExtensionsHook } from './features/plugins/extensions/usePluginExtensions'; import { importPanelPlugin, syncGetPanelPlugin } from './features/plugins/importPanelPlugin'; -import { PluginPreloadResult, preloadPlugins } from './features/plugins/pluginPreloader'; +import { preloadPlugins } from './features/plugins/pluginPreloader'; import { QueryRunner } from './features/query/state/QueryRunner'; import { runRequest } from './features/query/state/runRequest'; import { initWindowRuntime } from './features/runtime/init'; @@ -206,24 +207,26 @@ export class GrafanaApp { setDataSourceSrv(dataSourceSrv); initWindowRuntime(); - let preloadResults: PluginPreloadResult[] = []; + // Initialize plugin extensions + const extensionsRegistry = new ReactivePluginExtensionsRegistry(); + extensionsRegistry.register({ + pluginId: 'grafana', + extensionConfigs: getCoreExtensionConfigurations(), + }); if (contextSrv.user.orgRole !== '') { - // Preload selected app plugins - preloadResults = await preloadPlugins(config.apps); + // The "cloud-home-app" is registering banners once it's loaded, and this can cause a rerender in the AppChrome if it's loaded after the Grafana app init. + // TODO: remove the following exception once the issue mentioned above is fixed. + const awaitedAppPluginIds = ['cloud-home-app']; + const awaitedAppPlugins = Object.values(config.apps).filter((app) => awaitedAppPluginIds.includes(app.id)); + const appPlugins = Object.values(config.apps).filter((app) => !awaitedAppPluginIds.includes(app.id)); + + preloadPlugins(appPlugins, extensionsRegistry); + await preloadPlugins(awaitedAppPlugins, extensionsRegistry); } - // Create extension registry out of preloaded plugins and core extensions - const extensionRegistry = createPluginExtensionRegistry([ - { pluginId: 'grafana', extensionConfigs: getCoreExtensionConfigurations() }, - ...preloadResults, - ]); - - // Expose the getPluginExtension function via grafana-runtime - const pluginExtensionGetter: GetPluginExtensions = (options) => - getPluginExtensions({ ...options, registry: extensionRegistry }); - - setPluginExtensionGetter(pluginExtensionGetter); + setPluginExtensionGetter(createPluginExtensionsGetter(extensionsRegistry)); + setPluginExtensionsHook(createPluginExtensionsHook(extensionsRegistry)); // initialize chrome service const queryParams = locationService.getSearchObject(); diff --git a/public/app/core/components/AppChrome/AppChrome.test.tsx b/public/app/core/components/AppChrome/AppChrome.test.tsx index 870030ab187..7285224be50 100644 --- a/public/app/core/components/AppChrome/AppChrome.test.tsx +++ b/public/app/core/components/AppChrome/AppChrome.test.tsx @@ -16,7 +16,7 @@ import { AppChrome } from './AppChrome'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), - getPluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), + usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), })); const searchData: DataFrame = { diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 10d570e30f9..7cac56a2518 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -14,6 +14,7 @@ import { locationService, setBackendSrv, setDataSourceSrv, + usePluginLinkExtensions, } from '@grafana/runtime'; import { backendSrv } from 'app/core/services/backend_srv'; import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons'; @@ -57,6 +58,7 @@ import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), getPluginLinkExtensions: jest.fn(), + usePluginLinkExtensions: jest.fn(), useReturnToPrevious: jest.fn(), })); jest.mock('./api/buildInfo'); @@ -81,6 +83,7 @@ jest.spyOn(actions, 'rulesInSameGroupHaveInvalidFor').mockReturnValue([]); const mocks = { getAllDataSourcesMock: jest.mocked(config.getAllDataSources), getPluginLinkExtensionsMock: jest.mocked(getPluginLinkExtensions), + usePluginLinkExtensionsMock: jest.mocked(usePluginLinkExtensions), rulesInSameGroupHaveInvalidForMock: jest.mocked(actions.rulesInSameGroupHaveInvalidFor), api: { @@ -201,7 +204,7 @@ describe('RuleList', () => { AccessControlAction.AlertingRuleExternalWrite, ]); mocks.rulesInSameGroupHaveInvalidForMock.mockReturnValue([]); - mocks.getPluginLinkExtensionsMock.mockReturnValue({ + mocks.usePluginLinkExtensionsMock.mockReturnValue({ extensions: [ { pluginId: 'grafana-ml-app', @@ -213,6 +216,7 @@ describe('RuleList', () => { onClick: jest.fn(), }, ], + isLoading: false, }); }); diff --git a/public/app/features/alerting/unified/components/extensions/AlertInstanceExtensionPoint.tsx b/public/app/features/alerting/unified/components/extensions/AlertInstanceExtensionPoint.tsx index 760c25fbca4..a75f70af622 100644 --- a/public/app/features/alerting/unified/components/extensions/AlertInstanceExtensionPoint.tsx +++ b/public/app/features/alerting/unified/components/extensions/AlertInstanceExtensionPoint.tsx @@ -1,7 +1,7 @@ import React, { ReactElement, useMemo, useState } from 'react'; import { PluginExtensionLink, PluginExtensionPoints } from '@grafana/data'; -import { getPluginLinkExtensions } from '@grafana/runtime'; +import { usePluginLinkExtensions } from '@grafana/runtime'; import { Dropdown, IconButton } from '@grafana/ui'; import { ConfirmNavigationModal } from 'app/features/explore/extensions/ConfirmNavigationModal'; import { Alert, CombinedRule } from 'app/types/unified-alerting'; @@ -20,8 +20,8 @@ export const AlertInstanceExtensionPoint = ({ extensionPointId, }: AlertInstanceExtensionPointProps): ReactElement | null => { const [selectedExtension, setSelectedExtension] = useState(); - const context = { instance, rule }; - const extensions = useExtensionLinks(context, extensionPointId); + const context = useMemo(() => ({ instance, rule }), [instance, rule]); + const { extensions } = usePluginLinkExtensions({ context, extensionPointId, limitPerPlugin: 3 }); if (extensions.length === 0) { return null; @@ -48,18 +48,3 @@ export type PluginExtensionAlertInstanceContext = { rule?: CombinedRule; instance: Alert; }; - -function useExtensionLinks( - context: PluginExtensionAlertInstanceContext, - extensionPointId: PluginExtensionPoints -): PluginExtensionLink[] { - return useMemo(() => { - const { extensions } = getPluginLinkExtensions({ - extensionPointId, - context, - limitPerPlugin: 3, - }); - - return extensions; - }, [context, extensionPointId]); -} diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx index e5220ac4295..9f34c8c11c5 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx @@ -7,7 +7,7 @@ import { MemoryRouter } from 'react-router-dom'; import { byRole } from 'testing-library-selector'; import { PluginExtensionTypes } from '@grafana/data'; -import { getPluginLinkExtensions, setBackendSrv } from '@grafana/runtime'; +import { usePluginLinkExtensions, setBackendSrv } from '@grafana/runtime'; import { backendSrv } from 'app/core/services/backend_srv'; import { contextSrv } from 'app/core/services/context_srv'; import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; @@ -25,14 +25,14 @@ import { RuleDetails } from './RuleDetails'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), - getPluginLinkExtensions: jest.fn(), + usePluginLinkExtensions: jest.fn(), useReturnToPrevious: jest.fn(), })); jest.mock('../../hooks/useIsRuleEditable'); const mocks = { - getPluginLinkExtensionsMock: jest.mocked(getPluginLinkExtensions), + usePluginLinkExtensionsMock: jest.mocked(usePluginLinkExtensions), useIsRuleEditable: jest.mocked(useIsRuleEditable), }; @@ -68,7 +68,7 @@ afterAll(() => { }); beforeEach(() => { - mocks.getPluginLinkExtensionsMock.mockReturnValue({ + mocks.usePluginLinkExtensionsMock.mockReturnValue({ extensions: [ { pluginId: 'grafana-ml-app', @@ -80,6 +80,7 @@ beforeEach(() => { onClick: jest.fn(), }, ], + isLoading: false, }); server.resetHandlers(); mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse); diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.test.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.test.tsx index d9bfcb6b6e2..0770a0e184a 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.test.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { byLabelText, byRole, byTestId } from 'testing-library-selector'; import { PluginExtensionTypes } from '@grafana/data'; -import { getPluginLinkExtensions } from '@grafana/runtime'; +import { usePluginLinkExtensions } from '@grafana/runtime'; import { CombinedRuleNamespace } from '../../../../../types/unified-alerting'; import { GrafanaAlertState, PromAlertingRuleState } from '../../../../../types/unified-alerting-dto'; @@ -17,10 +17,11 @@ import { RuleDetailsMatchingInstances } from './RuleDetailsMatchingInstances'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), getPluginLinkExtensions: jest.fn(), + usePluginLinkExtensions: jest.fn(), })); const mocks = { - getPluginLinkExtensionsMock: jest.mocked(getPluginLinkExtensions), + usePluginLinkExtensionsMock: jest.mocked(usePluginLinkExtensions), }; const ui = { @@ -43,7 +44,7 @@ const ui = { describe('RuleDetailsMatchingInstances', () => { beforeEach(() => { - mocks.getPluginLinkExtensionsMock.mockReturnValue({ + mocks.usePluginLinkExtensionsMock.mockReturnValue({ extensions: [ { pluginId: 'grafana-ml-app', @@ -55,6 +56,7 @@ describe('RuleDetailsMatchingInstances', () => { onClick: jest.fn(), }, ], + isLoading: false, }); }); diff --git a/public/app/features/alerting/unified/home/PluginIntegrations.tsx b/public/app/features/alerting/unified/home/PluginIntegrations.tsx index a65b784211b..1e9baf298b6 100644 --- a/public/app/features/alerting/unified/home/PluginIntegrations.tsx +++ b/public/app/features/alerting/unified/home/PluginIntegrations.tsx @@ -3,14 +3,14 @@ import React from 'react'; import { PluginExtensionPoints } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data/'; -import { getPluginComponentExtensions } from '@grafana/runtime'; +import { usePluginComponentExtensions } from '@grafana/runtime'; import { Stack, Text } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui/'; export function PluginIntegrations() { const styles = useStyles2(getStyles); - const { extensions } = getPluginComponentExtensions({ + const { extensions } = usePluginComponentExtensions({ extensionPointId: PluginExtensionPoints.AlertingHomePage, limitPerPlugin: 1, }); diff --git a/public/app/features/commandPalette/actions/extensionActions.ts b/public/app/features/commandPalette/actions/extensionActions.ts deleted file mode 100644 index c3df191e021..00000000000 --- a/public/app/features/commandPalette/actions/extensionActions.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { PluginExtensionCommandPaletteContext, PluginExtensionPoints } from '@grafana/data'; -import { getPluginLinkExtensions } from '@grafana/runtime'; - -import { CommandPaletteAction } from '../types'; -import { EXTENSIONS_PRIORITY } from '../values'; - -export default function getExtensionActions(): CommandPaletteAction[] { - const context: PluginExtensionCommandPaletteContext = {}; - const { extensions } = getPluginLinkExtensions({ - extensionPointId: PluginExtensionPoints.CommandPalette, - context, - limitPerPlugin: 3, - }); - return extensions.map((extension) => ({ - section: extension.category ?? 'Extensions', - priority: EXTENSIONS_PRIORITY, - id: extension.id, - name: extension.title, - target: extension.path, - perform: () => extension.onClick && extension.onClick(), - })); -} diff --git a/public/app/features/commandPalette/actions/staticActions.ts b/public/app/features/commandPalette/actions/staticActions.ts index b9bc77118b8..ed82e1cf994 100644 --- a/public/app/features/commandPalette/actions/staticActions.ts +++ b/public/app/features/commandPalette/actions/staticActions.ts @@ -6,8 +6,6 @@ import { changeTheme } from 'app/core/services/theme'; import { CommandPaletteAction } from '../types'; import { ACTIONS_PRIORITY, DEFAULT_PRIORITY, PREFERENCES_PRIORITY } from '../values'; -import getExtensionActions from './extensionActions'; - // TODO: Clean this once ID is mandatory on nav items function idForNavItem(navItem: NavModelItem) { return 'navModel.' + navItem.id ?? navItem.url ?? navItem.text ?? navItem.subTitle; @@ -72,7 +70,7 @@ function navTreeToActions(navTree: NavModelItem[], parents: NavModelItem[] = []) return navActions; } -export default (navBarTree: NavModelItem[]): CommandPaletteAction[] => { +export default (navBarTree: NavModelItem[], extensionActions: CommandPaletteAction[]): CommandPaletteAction[] => { const globalActions: CommandPaletteAction[] = [ { id: 'preferences/theme', @@ -99,7 +97,6 @@ export default (navBarTree: NavModelItem[]): CommandPaletteAction[] => { }, ]; - const extensionActions = getExtensionActions(); const navBarActions = navTreeToActions(navBarTree); return [...globalActions, ...extensionActions, ...navBarActions]; diff --git a/public/app/features/commandPalette/actions/useActions.ts b/public/app/features/commandPalette/actions/useActions.ts index 19e83721171..44200cba273 100644 --- a/public/app/features/commandPalette/actions/useActions.ts +++ b/public/app/features/commandPalette/actions/useActions.ts @@ -6,18 +6,20 @@ import { CommandPaletteAction } from '../types'; import { getRecentDashboardActions } from './dashboardActions'; import getStaticActions from './staticActions'; +import useExtensionActions from './useExtensionActions'; export default function useActions(searchQuery: string) { const [navTreeActions, setNavTreeActions] = useState([]); const [recentDashboardActions, setRecentDashboardActions] = useState([]); + const extensionActions = useExtensionActions(); const navBarTree = useSelector((state) => state.navBarTree); // Load standard static actions useEffect(() => { - const staticActionsResp = getStaticActions(navBarTree); + const staticActionsResp = getStaticActions(navBarTree, extensionActions); setNavTreeActions(staticActionsResp); - }, [navBarTree]); + }, [navBarTree, extensionActions]); // Load recent dashboards - we don't want them to reload when the nav tree changes useEffect(() => { diff --git a/public/app/features/commandPalette/actions/useExtensionActions.ts b/public/app/features/commandPalette/actions/useExtensionActions.ts new file mode 100644 index 00000000000..3861b685793 --- /dev/null +++ b/public/app/features/commandPalette/actions/useExtensionActions.ts @@ -0,0 +1,29 @@ +import { useMemo } from 'react'; + +import { PluginExtensionCommandPaletteContext, PluginExtensionPoints } from '@grafana/data'; +import { usePluginLinkExtensions } from '@grafana/runtime'; + +import { CommandPaletteAction } from '../types'; +import { EXTENSIONS_PRIORITY } from '../values'; + +// NOTE: we are defining this here, as if we would define it in the hook, it would be recreated on every render, which would cause unnecessary re-renders. +const context: PluginExtensionCommandPaletteContext = {}; + +export default function useExtensionActions(): CommandPaletteAction[] { + const { extensions } = usePluginLinkExtensions({ + extensionPointId: PluginExtensionPoints.CommandPalette, + context, + limitPerPlugin: 3, + }); + + return useMemo(() => { + return extensions.map((extension) => ({ + section: extension.category ?? 'Extensions', + priority: EXTENSIONS_PRIORITY, + id: extension.id, + name: extension.title, + target: extension.path, + perform: () => extension.onClick && extension.onClick(), + })); + }, [extensions]); +} diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index 759cadc8d04..26d726f3d22 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -177,6 +177,8 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) { items.push(getInspectMenuItem(plugin, panel, dashboard)); + // TODO: make sure that this works reliably with the reactive extension registry + // (we need to be able to know in advance what extensions should be loaded for this extension point, and make it possible to await for them.) const { extensions } = getPluginLinkExtensions({ extensionPointId: PluginExtensionPoints.DashboardPanelMenu, context: createExtensionContext(panel, dashboard), diff --git a/public/app/features/dashboard/containers/DashboardPage.test.tsx b/public/app/features/dashboard/containers/DashboardPage.test.tsx index 004f1d951aa..5df1f991e5c 100644 --- a/public/app/features/dashboard/containers/DashboardPage.test.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.test.tsx @@ -68,6 +68,7 @@ jest.mock('app/core/core', () => ({ jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), getPluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), + usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), })); function getTestDashboard(overrides?: Partial, metaOverrides?: Partial): DashboardModel { diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuProvider.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuProvider.tsx index 1875dfdb1c4..a3bcda64ee4 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuProvider.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuProvider.tsx @@ -1,6 +1,13 @@ -import { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useMemo, useState } from 'react'; -import { LoadingState, PanelMenuItem } from '@grafana/data'; +import { + LoadingState, + PanelMenuItem, + PluginExtensionPanelContext, + PluginExtensionPoints, + getTimeZone, +} from '@grafana/data'; +import { usePluginLinkExtensions } from '@grafana/runtime'; import { getPanelStateForModel } from 'app/features/panel/state/selectors'; import { useSelector } from 'app/types'; @@ -21,10 +28,36 @@ interface Props { export function PanelHeaderMenuProvider({ panel, dashboard, loadingState, children }: Props) { const [items, setItems] = useState([]); const angularComponent = useSelector((state) => getPanelStateForModel(state, panel)?.angularComponent); + const context = useMemo(() => createExtensionContext(panel, dashboard), [panel, dashboard]); + const { extensions } = usePluginLinkExtensions({ + extensionPointId: PluginExtensionPoints.DashboardPanelMenu, + context, + limitPerPlugin: 3, + }); useEffect(() => { - setItems(getPanelMenu(dashboard, panel, angularComponent)); - }, [dashboard, panel, angularComponent, loadingState, setItems]); + setItems(getPanelMenu(dashboard, panel, extensions, angularComponent)); + }, [dashboard, panel, angularComponent, loadingState, setItems, extensions]); return children({ items }); } + +function createExtensionContext(panel: PanelModel, dashboard: DashboardModel): PluginExtensionPanelContext { + return { + id: panel.id, + pluginId: panel.type, + title: panel.title, + timeRange: dashboard.time, + timeZone: getTimeZone({ + timeZone: dashboard.timezone, + }), + dashboard: { + uid: dashboard.uid, + title: dashboard.title, + tags: Array.from(dashboard.tags), + }, + targets: panel.targets, + scopedVars: panel.scopedVars, + data: panel.getQueryRunner().getLastResult(), + }; +} diff --git a/public/app/features/dashboard/utils/getPanelMenu.test.ts b/public/app/features/dashboard/utils/getPanelMenu.test.ts index 6f25c38b942..162f716c49b 100644 --- a/public/app/features/dashboard/utils/getPanelMenu.test.ts +++ b/public/app/features/dashboard/utils/getPanelMenu.test.ts @@ -1,16 +1,7 @@ import { Store } from 'redux'; -import { - dateTime, - FieldType, - LoadingState, - PanelData, - PanelMenuItem, - PluginExtensionPanelContext, - PluginExtensionTypes, - toDataFrame, -} from '@grafana/data'; -import { AngularComponent, getPluginLinkExtensions } from '@grafana/runtime'; +import { PanelMenuItem, PluginExtensionLink, PluginExtensionTypes } from '@grafana/data'; +import { AngularComponent, usePluginLinkExtensions } from '@grafana/runtime'; import config from 'app/core/config'; import { grantUserPermissions } from 'app/features/alerting/unified/mocks'; import * as actions from 'app/features/explore/state/main'; @@ -31,16 +22,16 @@ jest.mock('app/core/services/context_srv', () => ({ jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), - setPluginExtensionGetter: jest.fn(), - getPluginLinkExtensions: jest.fn(), + setPluginExtensionsHook: jest.fn(), + usePluginLinkExtensions: jest.fn(), })); -const getPluginLinkExtensionsMock = jest.mocked(getPluginLinkExtensions); +const usePluginLinkExtensionsMock = jest.mocked(usePluginLinkExtensions); describe('getPanelMenu()', () => { beforeEach(() => { - getPluginLinkExtensionsMock.mockRestore(); - getPluginLinkExtensionsMock.mockReturnValue({ extensions: [] }); + usePluginLinkExtensionsMock.mockRestore(); + usePluginLinkExtensionsMock.mockReturnValue({ extensions: [], isLoading: false }); grantUserPermissions([AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleUpdate]); config.unifiedAlertingEnabled = false; }); @@ -48,8 +39,9 @@ describe('getPanelMenu()', () => { it('should return the correct panel menu items', () => { const panel = new PanelModel({}); const dashboard = createDashboardModelFixture({}); + const extensions: PluginExtensionLink[] = []; - const menuItems = getPanelMenu(dashboard, panel); + const menuItems = getPanelMenu(dashboard, panel, extensions); expect(menuItems).toMatchInlineSnapshot(` [ { @@ -126,22 +118,20 @@ describe('getPanelMenu()', () => { describe('when extending panel menu from plugins', () => { it('should contain menu item from link extension', () => { - getPluginLinkExtensionsMock.mockReturnValue({ - extensions: [ - { - id: '1', - pluginId: '...', - type: PluginExtensionTypes.link, - title: 'Declare incident', - description: 'Declaring an incident in the app', - path: '/a/grafana-basic-app/declare-incident', - }, - ], - }); + const extensions: PluginExtensionLink[] = [ + { + id: '1', + pluginId: '...', + type: PluginExtensionTypes.link, + title: 'Declare incident', + description: 'Declaring an incident in the app', + path: '/a/grafana-basic-app/declare-incident', + }, + ]; const panel = new PanelModel({}); const dashboard = createDashboardModelFixture({}); - const menuItems = getPanelMenu(dashboard, panel); + const menuItems = getPanelMenu(dashboard, panel, extensions); const extensionsSubMenu = menuItems.find((i) => i.text === 'Extensions')?.subMenu; expect(extensionsSubMenu).toEqual( @@ -155,22 +145,19 @@ describe('getPanelMenu()', () => { }); it('should truncate menu item title to 25 chars', () => { - getPluginLinkExtensionsMock.mockReturnValue({ - extensions: [ - { - id: '1', - pluginId: '...', - type: PluginExtensionTypes.link, - title: 'Declare incident when pressing this amazing menu item', - description: 'Declaring an incident in the app', - path: '/a/grafana-basic-app/declare-incident', - }, - ], - }); - const panel = new PanelModel({}); const dashboard = createDashboardModelFixture({}); - const menuItems = getPanelMenu(dashboard, panel); + const extensions: PluginExtensionLink[] = [ + { + id: '1', + pluginId: '...', + type: PluginExtensionTypes.link, + title: 'Declare incident when pressing this amazing menu item', + description: 'Declaring an incident in the app', + path: '/a/grafana-basic-app/declare-incident', + }, + ]; + const menuItems = getPanelMenu(dashboard, panel, extensions); const extensionsSubMenu = menuItems.find((i) => i.text === 'Extensions')?.subMenu; expect(extensionsSubMenu).toEqual( @@ -185,230 +172,42 @@ describe('getPanelMenu()', () => { it('should pass onClick from plugin extension link to menu item', () => { const expectedOnClick = jest.fn(); - - getPluginLinkExtensionsMock.mockReturnValue({ - extensions: [ - { - id: '1', - pluginId: '...', - type: PluginExtensionTypes.link, - title: 'Declare incident when pressing this amazing menu item', - description: 'Declaring an incident in the app', - onClick: expectedOnClick, - }, - ], - }); - const panel = new PanelModel({}); const dashboard = createDashboardModelFixture({}); - const menuItems = getPanelMenu(dashboard, panel); + const extensions: PluginExtensionLink[] = [ + { + id: '1', + pluginId: '...', + type: PluginExtensionTypes.link, + title: 'Declare incident when pressing this amazing menu item', + description: 'Declaring an incident in the app', + onClick: expectedOnClick, + }, + ]; + + const menuItems = getPanelMenu(dashboard, panel, extensions); const extensionsSubMenu = menuItems.find((i) => i.text === 'Extensions')?.subMenu; const menuItem = extensionsSubMenu?.find((i) => (i.text = 'Declare incident when...')); menuItem?.onClick?.({} as React.MouseEvent); - expect(expectedOnClick).toBeCalledTimes(1); - }); - - it('should pass context with correct values when configuring extension', () => { - const data: PanelData = { - series: [ - toDataFrame({ - fields: [ - { name: 'time', type: FieldType.time }, - { name: 'score', type: FieldType.number }, - ], - }), - ], - timeRange: { - from: dateTime(), - to: dateTime(), - raw: { - from: 'now', - to: 'now-1h', - }, - }, - state: LoadingState.Done, - }; - - const panel = new PanelModel({ - type: 'timeseries', - id: 1, - title: 'My panel', - targets: [ - { - refId: 'A', - datasource: { - type: 'testdata', - }, - }, - ], - scopedVars: { - a: { - text: 'a', - value: 'a', - }, - }, - queryRunner: { - getLastResult: jest.fn(() => data), - }, - }); - - const dashboard = createDashboardModelFixture({ - timezone: 'utc', - time: { - from: 'now-5m', - to: 'now', - }, - tags: ['database', 'panel'], - uid: '123', - title: 'My dashboard', - }); - - getPanelMenu(dashboard, panel); - - const context: PluginExtensionPanelContext = { - pluginId: 'timeseries', - id: 1, - title: 'My panel', - timeZone: 'utc', - timeRange: { - from: 'now-5m', - to: 'now', - }, - targets: [ - { - refId: 'A', - datasource: { - type: 'testdata', - }, - }, - ], - dashboard: { - tags: ['database', 'panel'], - uid: '123', - title: 'My dashboard', - }, - scopedVars: { - a: { - text: 'a', - value: 'a', - }, - }, - data, - }; - - expect(getPluginLinkExtensionsMock).toBeCalledWith(expect.objectContaining({ context })); - }); - - it('should pass context with default time zone values when configuring extension', () => { - const data: PanelData = { - series: [ - toDataFrame({ - fields: [ - { name: 'time', type: FieldType.time }, - { name: 'score', type: FieldType.number }, - ], - }), - ], - timeRange: { - from: dateTime(), - to: dateTime(), - raw: { - from: 'now', - to: 'now-1h', - }, - }, - state: LoadingState.Done, - }; - - const panel = new PanelModel({ - type: 'timeseries', - id: 1, - title: 'My panel', - targets: [ - { - refId: 'A', - datasource: { - type: 'testdata', - }, - }, - ], - scopedVars: { - a: { - text: 'a', - value: 'a', - }, - }, - queryRunner: { - getLastResult: jest.fn(() => data), - }, - }); - - const dashboard = createDashboardModelFixture({ - timezone: '', - time: { - from: 'now-5m', - to: 'now', - }, - tags: ['database', 'panel'], - uid: '123', - title: 'My dashboard', - }); - - getPanelMenu(dashboard, panel); - - const context: PluginExtensionPanelContext = { - pluginId: 'timeseries', - id: 1, - title: 'My panel', - timeZone: 'browser', - timeRange: { - from: 'now-5m', - to: 'now', - }, - targets: [ - { - refId: 'A', - datasource: { - type: 'testdata', - }, - }, - ], - dashboard: { - tags: ['database', 'panel'], - uid: '123', - title: 'My dashboard', - }, - scopedVars: { - a: { - text: 'a', - value: 'a', - }, - }, - data, - }; - - expect(getPluginLinkExtensionsMock).toBeCalledWith(expect.objectContaining({ context })); + expect(expectedOnClick).toHaveBeenCalledTimes(1); }); it('should contain menu item with category', () => { - getPluginLinkExtensionsMock.mockReturnValue({ - extensions: [ - { - id: '1', - pluginId: '...', - type: PluginExtensionTypes.link, - title: 'Declare incident', - description: 'Declaring an incident in the app', - path: '/a/grafana-basic-app/declare-incident', - category: 'Incident', - }, - ], - }); - const panel = new PanelModel({}); const dashboard = createDashboardModelFixture({}); - const menuItems = getPanelMenu(dashboard, panel); + const extensions: PluginExtensionLink[] = [ + { + id: '1', + pluginId: '...', + type: PluginExtensionTypes.link, + title: 'Declare incident', + description: 'Declaring an incident in the app', + path: '/a/grafana-basic-app/declare-incident', + category: 'Incident', + }, + ]; + const menuItems = getPanelMenu(dashboard, panel, extensions); const extensionsSubMenu = menuItems.find((i) => i.text === 'Extensions')?.subMenu; expect(extensionsSubMenu).toEqual( @@ -427,23 +226,20 @@ describe('getPanelMenu()', () => { }); it('should truncate category to 25 chars', () => { - getPluginLinkExtensionsMock.mockReturnValue({ - extensions: [ - { - id: '1', - pluginId: '...', - type: PluginExtensionTypes.link, - title: 'Declare incident', - description: 'Declaring an incident in the app', - path: '/a/grafana-basic-app/declare-incident', - category: 'Declare incident when pressing this amazing menu item', - }, - ], - }); - const panel = new PanelModel({}); const dashboard = createDashboardModelFixture({}); - const menuItems = getPanelMenu(dashboard, panel); + const extensions: PluginExtensionLink[] = [ + { + id: '1', + pluginId: '...', + type: PluginExtensionTypes.link, + title: 'Declare incident', + description: 'Declaring an incident in the app', + path: '/a/grafana-basic-app/declare-incident', + category: 'Declare incident when pressing this amazing menu item', + }, + ]; + const menuItems = getPanelMenu(dashboard, panel, extensions); const extensionsSubMenu = menuItems.find((i) => i.text === 'Extensions')?.subMenu; expect(extensionsSubMenu).toEqual( @@ -462,31 +258,28 @@ describe('getPanelMenu()', () => { }); it('should contain menu item with category and append items without category after divider', () => { - getPluginLinkExtensionsMock.mockReturnValue({ - extensions: [ - { - id: '1', - pluginId: '...', - type: PluginExtensionTypes.link, - title: 'Declare incident', - description: 'Declaring an incident in the app', - path: '/a/grafana-basic-app/declare-incident', - category: 'Incident', - }, - { - id: '2', - pluginId: '...', - type: PluginExtensionTypes.link, - title: 'Create forecast', - description: 'Declaring an incident in the app', - path: '/a/grafana-basic-app/declare-incident', - }, - ], - }); - const panel = new PanelModel({}); const dashboard = createDashboardModelFixture({}); - const menuItems = getPanelMenu(dashboard, panel); + const extensions: PluginExtensionLink[] = [ + { + id: '1', + pluginId: '...', + type: PluginExtensionTypes.link, + title: 'Declare incident', + description: 'Declaring an incident in the app', + path: '/a/grafana-basic-app/declare-incident', + category: 'Incident', + }, + { + id: '2', + pluginId: '...', + type: PluginExtensionTypes.link, + title: 'Create forecast', + description: 'Declaring an incident in the app', + path: '/a/grafana-basic-app/declare-incident', + }, + ]; + const menuItems = getPanelMenu(dashboard, panel, extensions); const extensionsSubMenu = menuItems.find((i) => i.text === 'Extensions')?.subMenu; expect(extensionsSubMenu).toEqual( @@ -519,8 +312,9 @@ describe('getPanelMenu()', () => { const angularComponent = { getScope: () => scope } as AngularComponent; const panel = new PanelModel({ isViewing: true }); const dashboard = createDashboardModelFixture({}); + const extensions: PluginExtensionLink[] = []; - const menuItems = getPanelMenu(dashboard, panel, angularComponent); + const menuItems = getPanelMenu(dashboard, panel, extensions, angularComponent); expect(menuItems).toMatchInlineSnapshot(` [ { @@ -590,7 +384,8 @@ describe('getPanelMenu()', () => { beforeAll(() => { const panel = new PanelModel({}); const dashboard = createDashboardModelFixture({}); - const menuItems = getPanelMenu(dashboard, panel); + const extensions: PluginExtensionLink[] = []; + const menuItems = getPanelMenu(dashboard, panel, extensions); explore = menuItems.find((item) => item.text === 'Explore') as PanelMenuItem; navigateSpy = jest.spyOn(actions, 'navigateToExplore'); window.open = windowOpen; @@ -624,14 +419,16 @@ describe('getPanelMenu()', () => { expect(windowOpen).toHaveBeenLastCalledWith(`${testSubUrl}${testUrl}`); }); }); + describe('Alerting menu', () => { it('should render "New alert rule" menu item if user has permissions to read and update alerts ', () => { const panel = new PanelModel({}); - const dashboard = createDashboardModelFixture({}); + const extensions: PluginExtensionLink[] = []; + config.unifiedAlertingEnabled = true; grantUserPermissions([AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleUpdate]); - const menuItems = getPanelMenu(dashboard, panel); + const menuItems = getPanelMenu(dashboard, panel, extensions); const moreSubMenu = menuItems.find((i) => i.text === 'More...')?.subMenu; expect(moreSubMenu).toEqual( @@ -646,11 +443,12 @@ describe('getPanelMenu()', () => { it('should not render "New alert rule" menu item, if user does not have permissions to update alerts ', () => { const panel = new PanelModel({}); const dashboard = createDashboardModelFixture({}); + const extensions: PluginExtensionLink[] = []; grantUserPermissions([AccessControlAction.AlertingRuleRead]); config.unifiedAlertingEnabled = true; - const menuItems = getPanelMenu(dashboard, panel); + const menuItems = getPanelMenu(dashboard, panel, extensions); const moreSubMenu = menuItems.find((i) => i.text === 'More...')?.subMenu; @@ -662,14 +460,16 @@ describe('getPanelMenu()', () => { ]) ); }); + it('should not render "New alert rule" menu item, if user does not have permissions to read update alerts ', () => { const panel = new PanelModel({}); - const dashboard = createDashboardModelFixture({}); + const extensions: PluginExtensionLink[] = []; + grantUserPermissions([]); config.unifiedAlertingEnabled = true; - const menuItems = getPanelMenu(dashboard, panel); + const menuItems = getPanelMenu(dashboard, panel, extensions); const moreSubMenu = menuItems.find((i) => i.text === 'More...')?.subMenu; const createAlertOption = moreSubMenu?.find((i) => i.text === 'New alert rule')?.subMenu; diff --git a/public/app/features/dashboard/utils/getPanelMenu.ts b/public/app/features/dashboard/utils/getPanelMenu.ts index 14ca792ff29..6dda0cc8b93 100644 --- a/public/app/features/dashboard/utils/getPanelMenu.ts +++ b/public/app/features/dashboard/utils/getPanelMenu.ts @@ -1,11 +1,5 @@ -import { - PanelMenuItem, - PluginExtensionPoints, - getTimeZone, - urlUtil, - type PluginExtensionPanelContext, -} from '@grafana/data'; -import { AngularComponent, getPluginLinkExtensions, locationService } from '@grafana/runtime'; +import { PanelMenuItem, urlUtil, PluginExtensionLink } from '@grafana/data'; +import { AngularComponent, locationService } from '@grafana/runtime'; import { PanelCtrl } from 'app/angular/panel/panel_ctrl'; import config from 'app/core/config'; import { createErrorNotification } from 'app/core/copy/appNotification'; @@ -42,6 +36,7 @@ import { getTimeSrv } from '../services/TimeSrv'; export function getPanelMenu( dashboard: DashboardModel, panel: PanelModel, + extensions: PluginExtensionLink[], angularComponent?: AngularComponent | null ): PanelMenuItem[] { const onViewPanel = (event: React.MouseEvent) => { @@ -332,12 +327,6 @@ export function getPanelMenu( }); } - const { extensions } = getPluginLinkExtensions({ - extensionPointId: PluginExtensionPoints.DashboardPanelMenu, - context: createExtensionContext(panel, dashboard), - limitPerPlugin: 3, - }); - if (extensions.length > 0 && !panel.isEditing) { menu.push({ text: 'Extensions', @@ -370,23 +359,3 @@ export function getPanelMenu( return menu; } - -function createExtensionContext(panel: PanelModel, dashboard: DashboardModel): PluginExtensionPanelContext { - return { - id: panel.id, - pluginId: panel.type, - title: panel.title, - timeRange: dashboard.time, - timeZone: getTimeZone({ - timeZone: dashboard.timezone, - }), - dashboard: { - uid: dashboard.uid, - title: dashboard.title, - tags: Array.from(dashboard.tags), - }, - targets: panel.targets, - scopedVars: panel.scopedVars, - data: panel.getQueryRunner().getLastResult(), - }; -} diff --git a/public/app/features/datasources/components/EditDataSource.test.tsx b/public/app/features/datasources/components/EditDataSource.test.tsx index 8ba9ff47546..5962d39b6c0 100644 --- a/public/app/features/datasources/components/EditDataSource.test.tsx +++ b/public/app/features/datasources/components/EditDataSource.test.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { Provider } from 'react-redux'; import { PluginExtensionTypes, PluginState } from '@grafana/data'; -import { setAngularLoader, setPluginExtensionGetter } from '@grafana/runtime'; +import { setAngularLoader, setPluginExtensionsHook } from '@grafana/runtime'; import { configureStore } from 'app/store/configureStore'; import { getMockDataSource, getMockDataSourceMeta, getMockDataSourceSettingsState } from '../__mocks__'; @@ -59,7 +59,7 @@ describe('', () => { }); beforeEach(() => { - setPluginExtensionGetter(jest.fn().mockReturnValue({ extensions: [] })); + setPluginExtensionsHook(jest.fn().mockReturnValue({ extensions: [] })); }); describe('On loading errors', () => { @@ -269,7 +269,7 @@ describe('', () => { it('should be possible to extend the form with a "component" extension in case the plugin ID is whitelisted', () => { const message = "I'm a UI extension component!"; - setPluginExtensionGetter( + setPluginExtensionsHook( jest.fn().mockReturnValue({ extensions: [ { @@ -298,7 +298,7 @@ describe('', () => { it('should NOT be possible to extend the form with a "component" extension in case the plugin ID is NOT whitelisted', () => { const message = "I'm a UI extension component!"; - setPluginExtensionGetter( + setPluginExtensionsHook( jest.fn().mockReturnValue({ extensions: [ { @@ -328,7 +328,7 @@ describe('', () => { const message = "I'm a UI extension component!"; const component = jest.fn().mockReturnValue(
{message}
); - setPluginExtensionGetter( + setPluginExtensionsHook( jest.fn().mockReturnValue({ extensions: [ { diff --git a/public/app/features/datasources/components/EditDataSource.tsx b/public/app/features/datasources/components/EditDataSource.tsx index e4802f21b49..b23508a020c 100644 --- a/public/app/features/datasources/components/EditDataSource.tsx +++ b/public/app/features/datasources/components/EditDataSource.tsx @@ -11,7 +11,7 @@ import { DataSourceJsonData, DataSourceUpdatedSuccessfully, } from '@grafana/data'; -import { getDataSourceSrv, getPluginComponentExtensions } from '@grafana/runtime'; +import { getDataSourceSrv, usePluginComponentExtensions } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { DataSourceSettingsState, useDispatch } from 'app/types'; @@ -136,15 +136,15 @@ export function EditDataSourceView({ onTest(); }; - const extensions = useMemo(() => { - const allowedPluginIds = ['grafana-pdc-app', 'grafana-auth-app']; - const extensionPointId = PluginExtensionPoints.DataSourceConfig; - const { extensions } = getPluginComponentExtensions<{ - context: PluginExtensionDataSourceConfigContext; - }>({ extensionPointId }); + const extensionPointId = PluginExtensionPoints.DataSourceConfig; + const { extensions } = usePluginComponentExtensions<{ + context: PluginExtensionDataSourceConfigContext; + }>({ extensionPointId }); + const allowedExtensions = useMemo(() => { + const allowedPluginIds = ['grafana-pdc-app', 'grafana-auth-app']; return extensions.filter((e) => allowedPluginIds.includes(e.pluginId)); - }, []); + }, [extensions]); if (loadError) { return ( @@ -203,7 +203,7 @@ export function EditDataSourceView({ )} {/* Extension point */} - {extensions.map((extension) => { + {allowedExtensions.map((extension) => { const Component = extension.component; return ( diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx index 1b9af90f81a..d004eb99473 100644 --- a/public/app/features/explore/Explore.test.tsx +++ b/public/app/features/explore/Explore.test.tsx @@ -5,7 +5,7 @@ import { TestProvider } from 'test/helpers/TestProvider'; import { CoreApp, createTheme, DataSourceApi, EventBusSrv, LoadingState, PluginExtensionTypes } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { getPluginLinkExtensions } from '@grafana/runtime'; +import { usePluginLinkExtensions } from '@grafana/runtime'; import { configureStore } from 'app/store/configureStore'; import { ContentOutlineContextProvider } from './ContentOutline/ContentOutlineContext'; @@ -123,7 +123,7 @@ jest.mock('app/core/core', () => ({ jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), - getPluginLinkExtensions: jest.fn(() => ({ extensions: [] })), + usePluginLinkExtensions: jest.fn(() => ({ extensions: [] })), })); // for the AutoSizer component to have a width @@ -137,7 +137,7 @@ jest.mock('react-virtualized-auto-sizer', () => { }); }); -const getPluginLinkExtensionsMock = jest.mocked(getPluginLinkExtensions); +const usePluginLinkExtensionsMock = jest.mocked(usePluginLinkExtensions); const setup = (overrideProps?: Partial) => { const store = configureStore({ @@ -179,7 +179,7 @@ describe('Explore', () => { }); it('should render toolbar extension point if extensions is available', async () => { - getPluginLinkExtensionsMock.mockReturnValueOnce({ + usePluginLinkExtensionsMock.mockReturnValueOnce({ extensions: [ { id: '1', @@ -198,6 +198,7 @@ describe('Explore', () => { onClick: () => {}, }, ], + isLoading: false, }); setup({ queryResponse: makeEmptyQueryResponse(LoadingState.Done) }); diff --git a/public/app/features/explore/extensions/ToolbarExtensionPoint.test.tsx b/public/app/features/explore/extensions/ToolbarExtensionPoint.test.tsx index 7d4e6c95c93..8dca4a16b8e 100644 --- a/public/app/features/explore/extensions/ToolbarExtensionPoint.test.tsx +++ b/public/app/features/explore/extensions/ToolbarExtensionPoint.test.tsx @@ -4,7 +4,7 @@ import React, { ReactNode } from 'react'; import { Provider } from 'react-redux'; import { PluginExtensionPoints, PluginExtensionTypes } from '@grafana/data'; -import { getPluginLinkExtensions } from '@grafana/runtime'; +import { usePluginLinkExtensions } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; import { contextSrv } from 'app/core/services/context_srv'; import { configureStore } from 'app/store/configureStore'; @@ -16,13 +16,13 @@ import { ToolbarExtensionPoint } from './ToolbarExtensionPoint'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), - getPluginLinkExtensions: jest.fn(), + usePluginLinkExtensions: jest.fn(), })); jest.mock('app/core/services/context_srv'); const contextSrvMock = jest.mocked(contextSrv); -const getPluginLinkExtensionsMock = jest.mocked(getPluginLinkExtensions); +const usePluginLinkExtensionsMock = jest.mocked(usePluginLinkExtensions); type storeOptions = { targets: DataQuery[]; @@ -54,7 +54,7 @@ function renderWithExploreStore( describe('ToolbarExtensionPoint', () => { describe('with extension points', () => { beforeAll(() => { - getPluginLinkExtensionsMock.mockReturnValue({ + usePluginLinkExtensionsMock.mockReturnValue({ extensions: [ { pluginId: 'grafana', @@ -74,6 +74,7 @@ describe('ToolbarExtensionPoint', () => { path: '/a/grafana-ml-ap/forecast', }, ], + isLoading: false, }); }); @@ -99,7 +100,9 @@ describe('ToolbarExtensionPoint', () => { await userEvent.click(screen.getByRole('button', { name: 'Add' })); await userEvent.click(screen.getByRole('menuitem', { name: 'Add to dashboard' })); - const { extensions } = getPluginLinkExtensions({ extensionPointId: PluginExtensionPoints.ExploreToolbarAction }); + const { extensions } = usePluginLinkExtensionsMock({ + extensionPointId: PluginExtensionPoints.ExploreToolbarAction, + }); const [extension] = extensions; expect(jest.mocked(extension.onClick)).toBeCalledTimes(1); @@ -125,7 +128,7 @@ describe('ToolbarExtensionPoint', () => { data, }); - const [options] = getPluginLinkExtensionsMock.mock.calls[0]; + const [options] = usePluginLinkExtensionsMock.mock.calls[0]; const { context } = options; expect(context).toEqual({ @@ -150,7 +153,7 @@ describe('ToolbarExtensionPoint', () => { data, }); - const [options] = getPluginLinkExtensionsMock.mock.calls[0]; + const [options] = usePluginLinkExtensionsMock.mock.calls[0]; const { context } = options; expect(context).toHaveProperty('timeZone', 'browser'); @@ -159,7 +162,7 @@ describe('ToolbarExtensionPoint', () => { it('should correct extension point id when fetching extensions', async () => { renderWithExploreStore(); - const [options] = getPluginLinkExtensionsMock.mock.calls[0]; + const [options] = usePluginLinkExtensionsMock.mock.calls[0]; const { extensionPointId } = options; expect(extensionPointId).toBe(PluginExtensionPoints.ExploreToolbarAction); @@ -168,7 +171,7 @@ describe('ToolbarExtensionPoint', () => { describe('with extension points without categories', () => { beforeAll(() => { - getPluginLinkExtensionsMock.mockReturnValue({ + usePluginLinkExtensionsMock.mockReturnValue({ extensions: [ { pluginId: 'grafana', @@ -187,6 +190,7 @@ describe('ToolbarExtensionPoint', () => { path: '/a/grafana-ml-ap/forecast', }, ], + isLoading: false, }); }); @@ -211,7 +215,7 @@ describe('ToolbarExtensionPoint', () => { describe('without extension points', () => { beforeAll(() => { contextSrvMock.hasPermission.mockReturnValue(true); - getPluginLinkExtensionsMock.mockReturnValue({ extensions: [] }); + usePluginLinkExtensionsMock.mockReturnValue({ extensions: [], isLoading: false }); }); it('should render "add to dashboard" action button if one pane is visible', async () => { @@ -229,7 +233,7 @@ describe('ToolbarExtensionPoint', () => { describe('with insufficient permissions', () => { beforeAll(() => { contextSrvMock.hasPermission.mockReturnValue(false); - getPluginLinkExtensionsMock.mockReturnValue({ extensions: [] }); + usePluginLinkExtensionsMock.mockReturnValue({ extensions: [], isLoading: false }); }); it('should not render "add to dashboard" action button', async () => { diff --git a/public/app/features/explore/extensions/ToolbarExtensionPoint.tsx b/public/app/features/explore/extensions/ToolbarExtensionPoint.tsx index 1a46d538547..477f413fa98 100644 --- a/public/app/features/explore/extensions/ToolbarExtensionPoint.tsx +++ b/public/app/features/explore/extensions/ToolbarExtensionPoint.tsx @@ -1,7 +1,7 @@ import React, { lazy, ReactElement, Suspense, useMemo, useState } from 'react'; import { type PluginExtensionLink, PluginExtensionPoints, RawTimeRange, getTimeZone } from '@grafana/data'; -import { getPluginLinkExtensions, config } from '@grafana/runtime'; +import { config, usePluginLinkExtensions } from '@grafana/runtime'; import { DataQuery, TimeZone } from '@grafana/schema'; import { Dropdown, ToolbarButton } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; @@ -26,7 +26,11 @@ export function ToolbarExtensionPoint(props: Props): ReactElement | null { const [selectedExtension, setSelectedExtension] = useState(); const [isOpen, setIsOpen] = useState(false); const context = useExtensionPointContext(props); - const extensions = useExtensionLinks(context); + const { extensions } = usePluginLinkExtensions({ + extensionPointId: PluginExtensionPoints.ExploreToolbarAction, + context: context, + limitPerPlugin: 3, + }); const selectExploreItem = getExploreItemSelector(exploreId); const noQueriesInPane = useSelector(selectExploreItem)?.queries?.length; @@ -114,15 +118,3 @@ function useExtensionPointContext(props: Props): PluginExtensionExploreContext { numUniqueIds, ]); } - -function useExtensionLinks(context: PluginExtensionExploreContext): PluginExtensionLink[] { - return useMemo(() => { - const { extensions } = getPluginLinkExtensions({ - extensionPointId: PluginExtensionPoints.ExploreToolbarAction, - context: context, - limitPerPlugin: 3, - }); - - return extensions; - }, [context]); -} diff --git a/public/app/features/explore/spec/helper/setup.tsx b/public/app/features/explore/spec/helper/setup.tsx index 6630602fa53..2611e1c30f1 100644 --- a/public/app/features/explore/spec/helper/setup.tsx +++ b/public/app/features/explore/spec/helper/setup.tsx @@ -22,7 +22,7 @@ import { locationService, HistoryWrapper, LocationService, - setPluginExtensionGetter, + setPluginExtensionsHook, setBackendSrv, getBackendSrv, getDataSourceSrv, @@ -86,7 +86,7 @@ export function setupExplore(options?: SetupOptions): { request: jest.fn().mockRejectedValue(undefined), }); - setPluginExtensionGetter(() => ({ extensions: [] })); + setPluginExtensionsHook(() => ({ extensions: [], isLoading: false })); // Clear this up otherwise it persists data source selection // TODO: probably add test for that too diff --git a/public/app/features/plugins/extensions/createPluginExtensionRegistry.test.ts b/public/app/features/plugins/extensions/createPluginExtensionRegistry.test.ts deleted file mode 100644 index 571b0f16243..00000000000 --- a/public/app/features/plugins/extensions/createPluginExtensionRegistry.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { PluginExtensionLinkConfig, PluginExtensionTypes } from '@grafana/data'; - -import { createPluginExtensionRegistry } from './createPluginExtensionRegistry'; - -describe('createRegistry()', () => { - const placement1 = 'grafana/dashboard/panel/menu'; - const placement2 = 'plugins/myorg-basic-app/start'; - const pluginId = 'grafana-basic-app'; - let link1: PluginExtensionLinkConfig, link2: PluginExtensionLinkConfig; - - beforeEach(() => { - link1 = { - type: PluginExtensionTypes.link, - title: 'Link 1', - description: 'Link 1 description', - path: `/a/${pluginId}/declare-incident`, - extensionPointId: placement1, - configure: jest.fn().mockReturnValue({}), - }; - link2 = { - type: PluginExtensionTypes.link, - title: 'Link 2', - description: 'Link 2 description', - path: `/a/${pluginId}/declare-incident`, - extensionPointId: placement2, - configure: jest.fn().mockImplementation((context) => ({ title: context?.title })), - }; - - global.console.warn = jest.fn(); - }); - - it('should be possible to register extensions', () => { - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link1, link2] }]); - - expect(Object.getOwnPropertyNames(registry)).toEqual([placement1, placement2]); - - // Placement 1 - expect(registry[placement1]).toHaveLength(1); - expect(registry[placement1]).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - pluginId, - config: { - ...link1, - configure: expect.any(Function), - }, - }), - ]) - ); - - // Placement 2 - expect(registry[placement2]).toHaveLength(1); - expect(registry[placement2]).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - pluginId, - config: { - ...link2, - configure: expect.any(Function), - }, - }), - ]) - ); - }); - - it('should not register link extensions with invalid path configured', () => { - const registry = createPluginExtensionRegistry([ - { pluginId, extensionConfigs: [{ ...link1, path: 'invalid-path' }, link2] }, - ]); - - expect(Object.getOwnPropertyNames(registry)).toEqual([placement2]); - - // Placement 2 - expect(registry[placement2]).toHaveLength(1); - expect(registry[placement2]).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - pluginId, - config: { - ...link2, - configure: expect.any(Function), - }, - }), - ]) - ); - }); - - it('should not register extensions for a plugin that had errors', () => { - const registry = createPluginExtensionRegistry([ - { pluginId, extensionConfigs: [link1, link2], error: new Error('Plugin failed to load') }, - ]); - - expect(Object.getOwnPropertyNames(registry)).toEqual([]); - }); - - it('should not register an extension if it has an invalid configure() function', () => { - const registry = createPluginExtensionRegistry([ - // @ts-ignore (We would like to provide an invalid configure function on purpose) - { pluginId, extensionConfigs: [{ ...link1, configure: '...' }, link2] }, - ]); - - expect(Object.getOwnPropertyNames(registry)).toEqual([placement2]); - - // Placement 2 (checking if it still registers the extension with a valid configuration) - expect(registry[placement2]).toHaveLength(1); - expect(registry[placement2]).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - pluginId, - config: { - ...link2, - configure: expect.any(Function), - }, - }), - ]) - ); - }); - - it('should not register an extension if it has invalid properties (empty title / description)', () => { - const registry = createPluginExtensionRegistry([ - { pluginId, extensionConfigs: [{ ...link1, title: '', description: '' }, link2] }, - ]); - - expect(Object.getOwnPropertyNames(registry)).toEqual([placement2]); - - // Placement 2 (checking if it still registers the extension with a valid configuration) - expect(registry[placement2]).toHaveLength(1); - expect(registry[placement2]).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - pluginId, - config: { - ...link2, - configure: expect.any(Function), - }, - }), - ]) - ); - }); -}); diff --git a/public/app/features/plugins/extensions/createPluginExtensionRegistry.ts b/public/app/features/plugins/extensions/createPluginExtensionRegistry.ts deleted file mode 100644 index 37090c8a736..00000000000 --- a/public/app/features/plugins/extensions/createPluginExtensionRegistry.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { PluginPreloadResult } from '../pluginPreloader'; - -import type { PluginExtensionRegistryItem, PluginExtensionRegistry } from './types'; -import { deepFreeze, logWarning } from './utils'; -import { isPluginExtensionConfigValid } from './validators'; - -export function createPluginExtensionRegistry(pluginPreloadResults: PluginPreloadResult[]): PluginExtensionRegistry { - const registry: PluginExtensionRegistry = {}; - - for (const { pluginId, extensionConfigs, error } of pluginPreloadResults) { - if (error) { - logWarning(`"${pluginId}" plugin failed to load, skip registering its extensions.`); - continue; - } - - for (const extensionConfig of extensionConfigs) { - const { extensionPointId } = extensionConfig; - - if (!extensionConfig || !isPluginExtensionConfigValid(pluginId, extensionConfig)) { - continue; - } - - let registryItem: PluginExtensionRegistryItem = { - config: extensionConfig, - - // Additional meta information about the extension - pluginId, - }; - - if (!Array.isArray(registry[extensionPointId])) { - registry[extensionPointId] = [registryItem]; - } else { - registry[extensionPointId].push(registryItem); - } - } - } - - return deepFreeze(registry); -} diff --git a/public/app/features/plugins/extensions/getPluginExtensions.test.tsx b/public/app/features/plugins/extensions/getPluginExtensions.test.tsx index 85c6304154e..c98b1e32772 100644 --- a/public/app/features/plugins/extensions/getPluginExtensions.test.tsx +++ b/public/app/features/plugins/extensions/getPluginExtensions.test.tsx @@ -3,8 +3,8 @@ import React from 'react'; import { PluginExtensionComponentConfig, PluginExtensionLinkConfig, PluginExtensionTypes } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; -import { createPluginExtensionRegistry } from './createPluginExtensionRegistry'; import { getPluginExtensions } from './getPluginExtensions'; +import { ReactivePluginExtensionsRegistry } from './reactivePluginExtensionRegistry'; import { isReadOnlyProxy } from './utils'; import { assertPluginExtensionLink } from './validators'; @@ -15,6 +15,19 @@ jest.mock('@grafana/runtime', () => { }; }); +function createPluginExtensionRegistry(preloadResults: Array<{ pluginId: string; extensionConfigs: any[] }>) { + const registry = new ReactivePluginExtensionsRegistry(); + + for (const { pluginId, extensionConfigs } of preloadResults) { + registry.register({ + pluginId, + extensionConfigs, + }); + } + + return registry.getRegistry(); +} + describe('getPluginExtensions()', () => { const extensionPoint1 = 'grafana/dashboard/panel/menu'; const extensionPoint2 = 'plugins/myorg-basic-app/start'; @@ -54,8 +67,8 @@ describe('getPluginExtensions()', () => { jest.mocked(reportInteraction).mockReset(); }); - test('should return the extensions for the given placement', () => { - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link1, link2] }]); + test('should return the extensions for the given placement', async () => { + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link1, link2] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint1 }); expect(extensions).toHaveLength(1); @@ -70,9 +83,11 @@ describe('getPluginExtensions()', () => { ); }); - test('should not limit the number of extensions per plugin by default', () => { + test('should not limit the number of extensions per plugin by default', async () => { // Registering 3 extensions for the same plugin for the same placement - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link1, link1, link1, link2] }]); + const registry = await createPluginExtensionRegistry([ + { pluginId, extensionConfigs: [link1, link1, link1, link2] }, + ]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint1 }); expect(extensions).toHaveLength(3); @@ -87,8 +102,8 @@ describe('getPluginExtensions()', () => { ); }); - test('should be possible to limit the number of extensions per plugin for a given placement', () => { - const registry = createPluginExtensionRegistry([ + test('should be possible to limit the number of extensions per plugin for a given placement', async () => { + const registry = await createPluginExtensionRegistry([ { pluginId, extensionConfigs: [link1, link1, link1, link2] }, { pluginId: 'my-plugin', @@ -116,16 +131,16 @@ describe('getPluginExtensions()', () => { ); }); - test('should return with an empty list if there are no extensions registered for a placement yet', () => { - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link1, link2] }]); + test('should return with an empty list if there are no extensions registered for a placement yet', async () => { + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link1, link2] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: 'placement-with-no-extensions' }); expect(extensions).toEqual([]); }); - test('should pass the context to the configure() function', () => { + test('should pass the context to the configure() function', async () => { const context = { title: 'New title from the context!' }; - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); getPluginExtensions({ registry, context, extensionPointId: extensionPoint2 }); @@ -133,7 +148,7 @@ describe('getPluginExtensions()', () => { expect(link2.configure).toHaveBeenCalledWith(context); }); - test('should be possible to update the basic properties with the configure() function', () => { + test('should be possible to update the basic properties with the configure() function', async () => { link2.configure = jest.fn().mockImplementation(() => ({ title: 'Updated title', description: 'Updated description', @@ -142,7 +157,7 @@ describe('getPluginExtensions()', () => { category: 'Machine Learning', })); - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint2 }); const [extension] = extensions; @@ -156,7 +171,7 @@ describe('getPluginExtensions()', () => { expect(extension.category).toBe('Machine Learning'); }); - test('should append link tracking to path when running configure() function', () => { + test('should append link tracking to path when running configure() function', async () => { link2.configure = jest.fn().mockImplementation(() => ({ title: 'Updated title', description: 'Updated description', @@ -165,7 +180,7 @@ describe('getPluginExtensions()', () => { category: 'Machine Learning', })); - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint2 }); const [extension] = extensions; @@ -177,7 +192,7 @@ describe('getPluginExtensions()', () => { ); }); - test('should ignore restricted properties passed via the configure() function', () => { + test('should ignore restricted properties passed via the configure() function', async () => { link2.configure = jest.fn().mockImplementation(() => ({ // The following props are not allowed to override type: 'unknown-type', @@ -190,7 +205,7 @@ describe('getPluginExtensions()', () => { title: 'test', })); - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint2 }); const [extension] = extensions; @@ -202,9 +217,9 @@ describe('getPluginExtensions()', () => { //@ts-ignore expect(extension.testing).toBeUndefined(); }); - test('should pass a read only context to the configure() function', () => { + test('should pass a read only context to the configure() function', async () => { const context = { title: 'New title from the context!' }; - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, context, extensionPointId: extensionPoint2 }); const [extension] = extensions; const readOnlyContext = (link2.configure as jest.Mock).mock.calls[0][0]; @@ -219,12 +234,12 @@ describe('getPluginExtensions()', () => { expect(context.title).toBe('New title from the context!'); }); - test('should catch errors in the configure() function and log them as warnings', () => { + test('should catch errors in the configure() function and log them as warnings', async () => { link2.configure = jest.fn().mockImplementation(() => { throw new Error('Something went wrong!'); }); - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); expect(() => { getPluginExtensions({ registry, extensionPointId: extensionPoint2 }); @@ -235,7 +250,7 @@ describe('getPluginExtensions()', () => { expect(global.console.warn).toHaveBeenCalledWith('[Plugin Extensions] Something went wrong!'); }); - test('should skip the link extension if the configure() function returns with an invalid path', () => { + test('should skip the link extension if the configure() function returns with an invalid path', async () => { link1.configure = jest.fn().mockImplementation(() => ({ path: '/a/another-plugin/page-a', })); @@ -243,7 +258,7 @@ describe('getPluginExtensions()', () => { path: 'invalid-path', })); - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link1, link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link1, link2] }]); const { extensions: extensionsAtPlacement1 } = getPluginExtensions({ registry, extensionPointId: extensionPoint1 }); const { extensions: extensionsAtPlacement2 } = getPluginExtensions({ registry, extensionPointId: extensionPoint2 }); @@ -255,7 +270,7 @@ describe('getPluginExtensions()', () => { expect(global.console.warn).toHaveBeenCalledTimes(2); }); - test('should skip the extension if any of the updated props returned by the configure() function are invalid', () => { + test('should skip the extension if any of the updated props returned by the configure() function are invalid', async () => { const overrides = { title: '', // Invalid empty string for title - should be ignored description: 'A valid description.', // This should be updated @@ -263,7 +278,7 @@ describe('getPluginExtensions()', () => { link2.configure = jest.fn().mockImplementation(() => overrides); - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint2 }); expect(extensions).toHaveLength(0); @@ -271,10 +286,10 @@ describe('getPluginExtensions()', () => { expect(global.console.warn).toHaveBeenCalledTimes(1); }); - test('should skip the extension if the configure() function returns a promise', () => { + test('should skip the extension if the configure() function returns a promise', async () => { link2.configure = jest.fn().mockImplementation(() => Promise.resolve({})); - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint2 }); expect(extensions).toHaveLength(0); @@ -282,24 +297,24 @@ describe('getPluginExtensions()', () => { expect(global.console.warn).toHaveBeenCalledTimes(1); }); - test('should skip (hide) the extension if the configure() function returns undefined', () => { + test('should skip (hide) the extension if the configure() function returns undefined', async () => { link2.configure = jest.fn().mockImplementation(() => undefined); - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint2 }); expect(extensions).toHaveLength(0); expect(global.console.warn).toHaveBeenCalledTimes(0); // As this is intentional, no warning should be logged }); - test('should pass event, context and helper to extension onClick()', () => { + test('should pass event, context and helper to extension onClick()', async () => { link2.path = undefined; link2.onClick = jest.fn().mockImplementation(() => { throw new Error('Something went wrong!'); }); const context = {}; - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint2 }); const [extension] = extensions; @@ -322,7 +337,7 @@ describe('getPluginExtensions()', () => { link2.path = undefined; link2.onClick = jest.fn().mockRejectedValue(new Error('testing')); - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint2 }); const [extension] = extensions; @@ -335,13 +350,13 @@ describe('getPluginExtensions()', () => { expect(global.console.warn).toHaveBeenCalledTimes(1); }); - test('should catch errors in the onClick() function and log them as warnings', () => { + test('should catch errors in the onClick() function and log them as warnings', async () => { link2.path = undefined; link2.onClick = jest.fn().mockImplementation(() => { throw new Error('Something went wrong!'); }); - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extensionPoint2 }); const [extension] = extensions; @@ -353,13 +368,13 @@ describe('getPluginExtensions()', () => { expect(global.console.warn).toHaveBeenCalledWith('[Plugin Extensions] Something went wrong!'); }); - test('should pass a read only context to the onClick() function', () => { + test('should pass a read only context to the onClick() function', async () => { const context = { title: 'New title from the context!' }; link2.path = undefined; link2.onClick = jest.fn(); - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); const { extensions } = getPluginExtensions({ registry, context, extensionPointId: extensionPoint2 }); const [extension] = extensions; @@ -375,14 +390,14 @@ describe('getPluginExtensions()', () => { }).toThrow(); }); - test('should not make original context read only', () => { + test('should not make original context read only', async () => { const context = { title: 'New title from the context!', nested: { title: 'title' }, array: ['a'], }; - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [link2] }]); getPluginExtensions({ registry, context, extensionPointId: extensionPoint2 }); expect(() => { @@ -392,10 +407,10 @@ describe('getPluginExtensions()', () => { }).not.toThrow(); }); - test('should report interaction when onClick is triggered', () => { + test('should report interaction when onClick is triggered', async () => { const reportInteractionMock = jest.mocked(reportInteraction); - const registry = createPluginExtensionRegistry([ + const registry = await createPluginExtensionRegistry([ { pluginId, extensionConfigs: [ @@ -423,9 +438,9 @@ describe('getPluginExtensions()', () => { }); }); - test('should be possible to register and get component type extensions', () => { + test('should be possible to register and get component type extensions', async () => { const extension = component1; - const registry = createPluginExtensionRegistry([{ pluginId, extensionConfigs: [extension] }]); + const registry = await createPluginExtensionRegistry([{ pluginId, extensionConfigs: [extension] }]); const { extensions } = getPluginExtensions({ registry, extensionPointId: extension.extensionPointId }); expect(extensions).toHaveLength(1); diff --git a/public/app/features/plugins/extensions/getPluginExtensions.ts b/public/app/features/plugins/extensions/getPluginExtensions.ts index 2db726b5e2a..17f5ec77633 100644 --- a/public/app/features/plugins/extensions/getPluginExtensions.ts +++ b/public/app/features/plugins/extensions/getPluginExtensions.ts @@ -8,8 +8,9 @@ import { type PluginExtensionComponent, urlUtil, } from '@grafana/data'; -import { reportInteraction } from '@grafana/runtime'; +import { GetPluginExtensions, reportInteraction } from '@grafana/runtime'; +import { ReactivePluginExtensionsRegistry } from './reactivePluginExtensionRegistry'; import type { PluginExtensionRegistry } from './types'; import { isPluginExtensionLinkConfig, @@ -40,10 +41,22 @@ type GetExtensions = ({ registry: PluginExtensionRegistry; }) => { extensions: PluginExtension[] }; +let registry: PluginExtensionRegistry = { id: '', extensions: {} }; + +export function createPluginExtensionsGetter(extensionRegistry: ReactivePluginExtensionsRegistry): GetPluginExtensions { + // Create a subscription to keep an copy of the registry state for use in the non-async + // plugin extensions getter. + extensionRegistry.asObservable().subscribe((r) => { + registry = r; + }); + + return (options) => getPluginExtensions({ ...options, registry }); +} + // Returns with a list of plugin extensions for the given extension point export const getPluginExtensions: GetExtensions = ({ context, extensionPointId, limitPerPlugin, registry }) => { const frozenContext = context ? getReadOnlyProxy(context) : {}; - const registryItems = registry[extensionPointId] ?? []; + const registryItems = registry.extensions[extensionPointId] ?? []; // We don't return the extensions separated by type, because in that case it would be much harder to define a sort-order for them. const extensions: PluginExtension[] = []; const extensionsByPlugin: Record = {}; diff --git a/public/app/features/plugins/extensions/reactivePluginExtensionRegistry.test.ts b/public/app/features/plugins/extensions/reactivePluginExtensionRegistry.test.ts new file mode 100644 index 00000000000..b958016764a --- /dev/null +++ b/public/app/features/plugins/extensions/reactivePluginExtensionRegistry.test.ts @@ -0,0 +1,682 @@ +import { firstValueFrom } from 'rxjs'; + +import { PluginExtensionTypes } from '@grafana/data'; + +import { ReactivePluginExtensionsRegistry } from './reactivePluginExtensionRegistry'; + +describe('createPluginExtensionsRegistry', () => { + const consoleWarn = jest.fn(); + + beforeEach(() => { + global.console.warn = consoleWarn; + consoleWarn.mockReset(); + }); + + it('should return empty registry when no extensions registered', async () => { + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + const observable = reactiveRegistry.asObservable(); + const registry = await firstValueFrom(observable); + expect(registry).toEqual({ + id: '', + extensions: {}, + }); + }); + + it('should generate an id for the registry once we register an extension to it', async () => { + const pluginId = 'grafana-basic-app'; + const extensionPointId = 'grafana/dashboard/panel/menu'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + + reactiveRegistry.register({ + pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId, + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry = await reactiveRegistry.getRegistry(); + + expect(registry.id).toBeDefined(); + expect(registry.extensions[extensionPointId]).toHaveLength(1); + }); + + it('should generate an a new id every time the registry changes', async () => { + const pluginId = 'grafana-basic-app'; + const extensionPointId = 'grafana/dashboard/panel/menu'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + + reactiveRegistry.register({ + pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId, + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry1 = await reactiveRegistry.getRegistry(); + const id1 = registry1.id; + + expect(id1).toBeDefined(); + + reactiveRegistry.register({ + pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 2', + description: 'Link 2 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId, + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry2 = await reactiveRegistry.getRegistry(); + const id2 = registry2.id; + + expect(id2).toBeDefined(); + expect(id2).not.toEqual(id1); + }); + + it('should be possible to register extensions in the registry', async () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + + reactiveRegistry.register({ + pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + { + type: PluginExtensionTypes.link, + title: 'Link 2', + description: 'Link 2 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'plugins/myorg-basic-app/start', + configure: jest.fn().mockImplementation((context) => ({ title: context?.title })), + }, + ], + }); + + const registry = await reactiveRegistry.getRegistry(); + + expect(registry.extensions).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId, + config: { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + ], + 'plugins/myorg-basic-app/start': [ + { + pluginId: pluginId, + config: { + type: PluginExtensionTypes.link, + title: 'Link 2', + description: 'Link 2 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'plugins/myorg-basic-app/start', + configure: expect.any(Function), + }, + }, + ], + }); + }); + + it('should be possible to asynchronously register extensions for the same placement (different plugins)', async () => { + const pluginId1 = 'grafana-basic-app'; + const pluginId2 = 'grafana-basic-app2'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + + // Register extensions for the first plugin + reactiveRegistry.register({ + pluginId: pluginId1, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId1}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry1 = await reactiveRegistry.getRegistry(); + + expect(registry1.extensions).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId1, + config: { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId1}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + ], + }); + + // Register extensions for the second plugin to a different placement + reactiveRegistry.register({ + pluginId: pluginId2, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 2', + description: 'Link 2 description', + path: `/a/${pluginId2}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry2 = await reactiveRegistry.getRegistry(); + + expect(registry2.extensions).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId1, + config: { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId1}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + { + pluginId: pluginId2, + config: { + type: PluginExtensionTypes.link, + title: 'Link 2', + description: 'Link 2 description', + path: `/a/${pluginId2}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + ], + }); + }); + + it('should be possible to asynchronously register extensions for a different placement (different plugin)', async () => { + const pluginId1 = 'grafana-basic-app'; + const pluginId2 = 'grafana-basic-app2'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + + // Register extensions for the first plugin + reactiveRegistry.register({ + pluginId: pluginId1, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId1}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry1 = await reactiveRegistry.getRegistry(); + + expect(registry1.extensions).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId1, + config: { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId1}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + ], + }); + + // Register extensions for the second plugin to a different placement + reactiveRegistry.register({ + pluginId: pluginId2, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 2', + description: 'Link 2 description', + path: `/a/${pluginId2}/declare-incident`, + extensionPointId: 'plugins/myorg-basic-app/start', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry2 = await reactiveRegistry.getRegistry(); + + expect(registry2.extensions).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId1, + config: { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId1}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + ], + 'plugins/myorg-basic-app/start': [ + { + pluginId: pluginId2, + config: { + type: PluginExtensionTypes.link, + title: 'Link 2', + description: 'Link 2 description', + path: `/a/${pluginId2}/declare-incident`, + extensionPointId: 'plugins/myorg-basic-app/start', + configure: expect.any(Function), + }, + }, + ], + }); + }); + + it('should be possible to asynchronously register extensions for the same placement (same plugin)', async () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + + // Register extensions for the first extension point + reactiveRegistry.register({ + pluginId: pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident-1`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + // Register extensions to a different extension point + reactiveRegistry.register({ + pluginId: pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 2', + description: 'Link 2 description', + path: `/a/${pluginId}/declare-incident-2`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry2 = await reactiveRegistry.getRegistry(); + + expect(registry2.extensions).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId, + config: { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident-1`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + { + pluginId: pluginId, + config: { + type: PluginExtensionTypes.link, + title: 'Link 2', + description: 'Link 2 description', + path: `/a/${pluginId}/declare-incident-2`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + ], + }); + }); + + it('should be possible to asynchronously register extensions for a different placement (same plugin)', async () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + + // Register extensions for the first extension point + reactiveRegistry.register({ + pluginId: pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + // Register extensions to a different extension point + reactiveRegistry.register({ + pluginId: pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 2', + description: 'Link 2 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'plugins/myorg-basic-app/start', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + const registry2 = await reactiveRegistry.getRegistry(); + + expect(registry2.extensions).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId, + config: { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + ], + 'plugins/myorg-basic-app/start': [ + { + pluginId: pluginId, + config: { + type: PluginExtensionTypes.link, + title: 'Link 2', + description: 'Link 2 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'plugins/myorg-basic-app/start', + configure: expect.any(Function), + }, + }, + ], + }); + }); + + it('should notify subscribers when the registry changes', async () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + const observable = reactiveRegistry.asObservable(); + const subscribeCallback = jest.fn(); + + observable.subscribe(subscribeCallback); + + // Register extensions for the first plugin + reactiveRegistry.register({ + pluginId: pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + expect(subscribeCallback).toHaveBeenCalledTimes(2); + + // Register extensions for the first plugin + reactiveRegistry.register({ + pluginId: 'another-plugin', + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/another-plugin/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + expect(subscribeCallback).toHaveBeenCalledTimes(3); + + const registry = subscribeCallback.mock.calls[2][0]; + + expect(registry.extensions).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId, + config: { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + { + pluginId: 'another-plugin', + config: { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/another-plugin/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + ], + }); + }); + + it('should give the last version of the registry for new subscribers', async () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + const observable = reactiveRegistry.asObservable(); + const subscribeCallback = jest.fn(); + + reactiveRegistry.register({ + pluginId: pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + observable.subscribe(subscribeCallback); + expect(subscribeCallback).toHaveBeenCalledTimes(1); + + const registry = subscribeCallback.mock.calls[0][0]; + + expect(registry.extensions).toEqual({ + 'grafana/dashboard/panel/menu': [ + { + pluginId: pluginId, + config: { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: expect.any(Function), + }, + }, + ], + }); + }); + + it('should not register extensions for a plugin that had errors', () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + const observable = reactiveRegistry.asObservable(); + const subscribeCallback = jest.fn(); + + reactiveRegistry.register({ + error: new Error('Something is broken'), + pluginId: pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + expect(consoleWarn).toHaveBeenCalled(); + + observable.subscribe(subscribeCallback); + expect(subscribeCallback).toHaveBeenCalledTimes(1); + + const registry = subscribeCallback.mock.calls[0][0]; + expect(registry.extensions).toEqual({}); + }); + + it('should not register an extension if it has an invalid configure() function', () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + const observable = reactiveRegistry.asObservable(); + const subscribeCallback = jest.fn(); + + reactiveRegistry.register({ + pluginId: pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Link 1', + description: 'Link 1 description', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + //@ts-ignore + configure: '...', + }, + ], + }); + + expect(consoleWarn).toHaveBeenCalled(); + + observable.subscribe(subscribeCallback); + expect(subscribeCallback).toHaveBeenCalledTimes(1); + + const registry = subscribeCallback.mock.calls[0][0]; + expect(registry.extensions).toEqual({}); + }); + + it('should not register an extension if it has invalid properties (empty title / description)', () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + const observable = reactiveRegistry.asObservable(); + const subscribeCallback = jest.fn(); + + reactiveRegistry.register({ + pluginId: pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: '', + description: '', + path: `/a/${pluginId}/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + expect(consoleWarn).toHaveBeenCalled(); + + observable.subscribe(subscribeCallback); + expect(subscribeCallback).toHaveBeenCalledTimes(1); + + const registry = subscribeCallback.mock.calls[0][0]; + expect(registry.extensions).toEqual({}); + }); + + it('should not register link extensions with invalid path configured', () => { + const pluginId = 'grafana-basic-app'; + const reactiveRegistry = new ReactivePluginExtensionsRegistry(); + const observable = reactiveRegistry.asObservable(); + const subscribeCallback = jest.fn(); + + reactiveRegistry.register({ + pluginId: pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + title: 'Title 1', + description: 'Description 1', + path: `/a/another-plugin/declare-incident`, + extensionPointId: 'grafana/dashboard/panel/menu', + configure: jest.fn().mockReturnValue({}), + }, + ], + }); + + expect(consoleWarn).toHaveBeenCalled(); + + observable.subscribe(subscribeCallback); + expect(subscribeCallback).toHaveBeenCalledTimes(1); + + const registry = subscribeCallback.mock.calls[0][0]; + expect(registry.extensions).toEqual({}); + }); +}); diff --git a/public/app/features/plugins/extensions/reactivePluginExtensionRegistry.ts b/public/app/features/plugins/extensions/reactivePluginExtensionRegistry.ts new file mode 100644 index 00000000000..016b97e0a4b --- /dev/null +++ b/public/app/features/plugins/extensions/reactivePluginExtensionRegistry.ts @@ -0,0 +1,79 @@ +import { Observable, ReplaySubject, Subject, firstValueFrom, map, scan, startWith } from 'rxjs'; +import { v4 as uuidv4 } from 'uuid'; + +import { PluginPreloadResult } from '../pluginPreloader'; + +import { PluginExtensionRegistry, PluginExtensionRegistryItem } from './types'; +import { deepFreeze, logWarning } from './utils'; +import { isPluginExtensionConfigValid } from './validators'; + +export class ReactivePluginExtensionsRegistry { + private resultSubject: Subject; + private registrySubject: ReplaySubject; + + constructor() { + this.resultSubject = new Subject(); + // This is the subject that we expose. + // (It will buffer the last value on the stream - the registry - and emit it to new subscribers immediately.) + this.registrySubject = new ReplaySubject(1); + + this.resultSubject + .pipe( + scan(resultsToRegistry, { id: '', extensions: {} }), + // Emit an empty registry to start the stream (it is only going to do it once during construction, and then just passes down the values) + startWith({ id: '', extensions: {} }), + map((registry) => deepFreeze(registry)) + ) + // Emitting the new registry to `this.registrySubject` + .subscribe(this.registrySubject); + } + + register(result: PluginPreloadResult): void { + this.resultSubject.next(result); + } + + asObservable(): Observable { + return this.registrySubject.asObservable(); + } + + getRegistry(): Promise { + return firstValueFrom(this.asObservable()); + } +} + +function resultsToRegistry(registry: PluginExtensionRegistry, result: PluginPreloadResult): PluginExtensionRegistry { + const { pluginId, extensionConfigs, error } = result; + + // TODO: We should probably move this section to where we load the plugin since this is only used + // to provide a log to the user. + if (error) { + logWarning(`"${pluginId}" plugin failed to load, skip registering its extensions.`); + return registry; + } + + for (const extensionConfig of extensionConfigs) { + const { extensionPointId } = extensionConfig; + + if (!extensionConfig || !isPluginExtensionConfigValid(pluginId, extensionConfig)) { + return registry; + } + + let registryItem: PluginExtensionRegistryItem = { + config: extensionConfig, + + // Additional meta information about the extension + pluginId, + }; + + if (!Array.isArray(registry.extensions[extensionPointId])) { + registry.extensions[extensionPointId] = [registryItem]; + } else { + registry.extensions[extensionPointId].push(registryItem); + } + } + + // Add a unique ID to the registry (the registry object itself is immutable) + registry.id = uuidv4(); + + return registry; +} diff --git a/public/app/features/plugins/extensions/types.ts b/public/app/features/plugins/extensions/types.ts index b5e5e31cc7e..9642cc6627d 100644 --- a/public/app/features/plugins/extensions/types.ts +++ b/public/app/features/plugins/extensions/types.ts @@ -9,4 +9,7 @@ export type PluginExtensionRegistryItem = { }; // A map of placement names to a list of extensions -export type PluginExtensionRegistry = Record; +export type PluginExtensionRegistry = { + id: string; + extensions: Record; +}; diff --git a/public/app/features/plugins/extensions/usePluginExtensions.test.tsx b/public/app/features/plugins/extensions/usePluginExtensions.test.tsx new file mode 100644 index 00000000000..49f29e4b1b1 --- /dev/null +++ b/public/app/features/plugins/extensions/usePluginExtensions.test.tsx @@ -0,0 +1,225 @@ +import { act } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; + +import { PluginExtensionTypes } from '@grafana/data'; + +import { ReactivePluginExtensionsRegistry } from './reactivePluginExtensionRegistry'; +import { createPluginExtensionsHook } from './usePluginExtensions'; + +describe('usePluginExtensions()', () => { + let reactiveRegistry: ReactivePluginExtensionsRegistry; + + beforeEach(() => { + reactiveRegistry = new ReactivePluginExtensionsRegistry(); + }); + + it('should return an empty array if there are no extensions registered for the extension point', () => { + const usePluginExtensions = createPluginExtensionsHook(reactiveRegistry); + const { result } = renderHook(() => + usePluginExtensions({ + extensionPointId: 'foo/bar', + }) + ); + + expect(result.current.extensions).toEqual([]); + }); + + it('should return the plugin extensions from the registry', () => { + const extensionPointId = 'plugins/foo/bar'; + const pluginId = 'my-app-plugin'; + + reactiveRegistry.register({ + pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + extensionPointId, + title: '1', + description: '1', + path: `/a/${pluginId}/2`, + }, + { + type: PluginExtensionTypes.link, + extensionPointId, + title: '2', + description: '2', + path: `/a/${pluginId}/2`, + }, + ], + }); + + const usePluginExtensions = createPluginExtensionsHook(reactiveRegistry); + const { result } = renderHook(() => usePluginExtensions({ extensionPointId })); + + expect(result.current.extensions.length).toBe(2); + expect(result.current.extensions[0].title).toBe('1'); + expect(result.current.extensions[1].title).toBe('2'); + }); + + it('should dynamically update the extensions registered for a certain extension point', () => { + const extensionPointId = 'plugins/foo/bar'; + const pluginId = 'my-app-plugin'; + const usePluginExtensions = createPluginExtensionsHook(reactiveRegistry); + let { result, rerender } = renderHook(() => usePluginExtensions({ extensionPointId })); + + // No extensions yet + expect(result.current.extensions.length).toBe(0); + + // Add extensions to the registry + act(() => { + reactiveRegistry.register({ + pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + extensionPointId, + title: '1', + description: '1', + path: `/a/${pluginId}/2`, + }, + { + type: PluginExtensionTypes.link, + extensionPointId, + title: '2', + description: '2', + path: `/a/${pluginId}/2`, + }, + ], + }); + }); + + // Check if the hook returns the new extensions + rerender(); + + expect(result.current.extensions.length).toBe(2); + expect(result.current.extensions[0].title).toBe('1'); + expect(result.current.extensions[1].title).toBe('2'); + }); + + it('should only render the hook once', () => { + const spy = jest.spyOn(reactiveRegistry, 'asObservable'); + const extensionPointId = 'plugins/foo/bar'; + const usePluginExtensions = createPluginExtensionsHook(reactiveRegistry); + + renderHook(() => usePluginExtensions({ extensionPointId })); + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('should return the same extensions object if the context object is the same', () => { + const extensionPointId = 'plugins/foo/bar'; + const pluginId = 'my-app-plugin'; + const usePluginExtensions = createPluginExtensionsHook(reactiveRegistry); + + // Add extensions to the registry + act(() => { + reactiveRegistry.register({ + pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + extensionPointId, + title: '1', + description: '1', + path: `/a/${pluginId}/2`, + }, + { + type: PluginExtensionTypes.link, + extensionPointId, + title: '2', + description: '2', + path: `/a/${pluginId}/2`, + }, + ], + }); + }); + + // Check if it returns the same extensions object in case nothing changes + const context = {}; + const firstResults = renderHook(() => usePluginExtensions({ extensionPointId, context })); + const secondResults = renderHook(() => usePluginExtensions({ extensionPointId, context })); + expect(firstResults.result.current.extensions === secondResults.result.current.extensions).toBe(true); + }); + + it('should return a new extensions object if the context object is different', () => { + const extensionPointId = 'plugins/foo/bar'; + const pluginId = 'my-app-plugin'; + const usePluginExtensions = createPluginExtensionsHook(reactiveRegistry); + + // Add extensions to the registry + act(() => { + reactiveRegistry.register({ + pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + extensionPointId, + title: '1', + description: '1', + path: `/a/${pluginId}/2`, + }, + { + type: PluginExtensionTypes.link, + extensionPointId, + title: '2', + description: '2', + path: `/a/${pluginId}/2`, + }, + ], + }); + }); + + // Check if it returns a different extensions object in case the context object changes + const firstResults = renderHook(() => usePluginExtensions({ extensionPointId, context: {} })); + const secondResults = renderHook(() => usePluginExtensions({ extensionPointId, context: {} })); + expect(firstResults.result.current.extensions === secondResults.result.current.extensions).toBe(false); + }); + + it('should return a new extensions object if the registry changes but the context object is the same', () => { + const extensionPointId = 'plugins/foo/bar'; + const pluginId = 'my-app-plugin'; + const context = {}; + const usePluginExtensions = createPluginExtensionsHook(reactiveRegistry); + + // Add the first extension + act(() => { + reactiveRegistry.register({ + pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + extensionPointId, + title: '1', + description: '1', + path: `/a/${pluginId}/2`, + }, + ], + }); + }); + + const { result, rerender } = renderHook(() => usePluginExtensions({ extensionPointId, context })); + const firstExtensions = result.current.extensions; + + // Add the second extension + act(() => { + reactiveRegistry.register({ + pluginId, + extensionConfigs: [ + { + type: PluginExtensionTypes.link, + extensionPointId, + // extensionPointId: 'plugins/foo/bar/zed', // A different extension point (to be sure that it's also returning a new object when the actual extension point doesn't change) + title: '2', + description: '2', + path: `/a/${pluginId}/2`, + }, + ], + }); + }); + + rerender(); + + const secondExtensions = result.current.extensions; + + expect(firstExtensions === secondExtensions).toBe(false); + }); +}); diff --git a/public/app/features/plugins/extensions/usePluginExtensions.tsx b/public/app/features/plugins/extensions/usePluginExtensions.tsx new file mode 100644 index 00000000000..4eb55c700d3 --- /dev/null +++ b/public/app/features/plugins/extensions/usePluginExtensions.tsx @@ -0,0 +1,54 @@ +import { useObservable } from 'react-use'; + +import { PluginExtension } from '@grafana/data'; +import { GetPluginExtensionsOptions, UsePluginExtensionsResult } from '@grafana/runtime'; + +import { getPluginExtensions } from './getPluginExtensions'; +import { ReactivePluginExtensionsRegistry } from './reactivePluginExtensionRegistry'; + +export function createPluginExtensionsHook(extensionsRegistry: ReactivePluginExtensionsRegistry) { + const observableRegistry = extensionsRegistry.asObservable(); + const cache: { + id: string; + extensions: Record; + } = { + id: '', + extensions: {}, + }; + + return function usePluginExtensions(options: GetPluginExtensionsOptions): UsePluginExtensionsResult { + const registry = useObservable(observableRegistry); + + if (!registry) { + return { extensions: [], isLoading: false }; + } + + if (registry.id !== cache.id) { + cache.id = registry.id; + cache.extensions = {}; + } + + // `getPluginExtensions` will return a new array of objects even if it is called with the same options, as it always constructing a frozen objects. + // Due to this we are caching the result of `getPluginExtensions` to avoid unnecessary re-renders for components that are using this hook. + // (NOTE: we are only checking referential equality of `context` object, so it is important to not mutate the object passed to this hook.) + const key = `${options.extensionPointId}-${options.limitPerPlugin}`; + if (cache.extensions[key] && cache.extensions[key].context === options.context) { + return { + extensions: cache.extensions[key].extensions, + isLoading: false, + }; + } + + const { extensions } = getPluginExtensions({ ...options, registry }); + + cache.extensions[key] = { + context: options.context, + extensions, + }; + + return { + extensions, + isLoading: false, + }; + }; +} diff --git a/public/app/features/plugins/pluginPreloader.ts b/public/app/features/plugins/pluginPreloader.ts index 6306a443183..25ea2f1f11a 100644 --- a/public/app/features/plugins/pluginPreloader.ts +++ b/public/app/features/plugins/pluginPreloader.ts @@ -3,6 +3,7 @@ import type { AppPluginConfig } from '@grafana/runtime'; import { startMeasure, stopMeasure } from 'app/core/utils/metrics'; import { getPluginSettings } from 'app/features/plugins/pluginSettings'; +import { ReactivePluginExtensionsRegistry } from './extensions/reactivePluginExtensionRegistry'; import * as pluginLoader from './plugin_loader'; export type PluginPreloadResult = { @@ -11,12 +12,16 @@ export type PluginPreloadResult = { extensionConfigs: PluginExtensionConfig[]; }; -export async function preloadPlugins(apps: Record = {}): Promise { +export async function preloadPlugins(apps: AppPluginConfig[] = [], registry: ReactivePluginExtensionsRegistry) { startMeasure('frontend_plugins_preload'); - const pluginsToPreload = Object.values(apps).filter((app) => app.preload); - const result = await Promise.all(pluginsToPreload.map(preload)); + const promises = apps.filter((config) => config.preload).map((config) => preload(config)); + const preloadedPlugins = await Promise.all(promises); + + for (const preloadedPlugin of preloadedPlugins) { + registry.register(preloadedPlugin); + } + stopMeasure('frontend_plugins_preload'); - return result; } async function preload(config: AppPluginConfig): Promise { diff --git a/public/app/features/profile/UserProfileEditPage.test.tsx b/public/app/features/profile/UserProfileEditPage.test.tsx index 1f69b45c495..e81b5bfc50e 100644 --- a/public/app/features/profile/UserProfileEditPage.test.tsx +++ b/public/app/features/profile/UserProfileEditPage.test.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { OrgRole, PluginExtensionComponent, PluginExtensionTypes } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { setPluginExtensionGetter, GetPluginExtensions } from '@grafana/runtime'; +import { setPluginExtensionsHook, UsePluginExtensions } from '@grafana/runtime'; import * as useQueryParams from 'app/core/hooks/useQueryParams'; import { TestProvider } from '../../../test/helpers/TestProvider'; @@ -170,9 +170,11 @@ async function getTestContext(overrides: Partial = jest.fn().mockReturnValue({ extensions }); + const getter: UsePluginExtensions = jest + .fn() + .mockReturnValue({ extensions, isLoading: false }); - setPluginExtensionGetter(getter); + setPluginExtensionsHook(getter); const props = { ...defaultProps, ...overrides }; const { rerender } = render( diff --git a/public/app/features/profile/UserProfileEditPage.tsx b/public/app/features/profile/UserProfileEditPage.tsx index 2e99bca080f..613bf924891 100644 --- a/public/app/features/profile/UserProfileEditPage.tsx +++ b/public/app/features/profile/UserProfileEditPage.tsx @@ -1,10 +1,10 @@ -import React, { useMemo, useState } from 'react'; +import React, { useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { useMount } from 'react-use'; import { PluginExtensionComponent, PluginExtensionPoints } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { getPluginComponentExtensions } from '@grafana/runtime'; +import { usePluginComponentExtensions } from '@grafana/runtime'; import { Tab, TabsBar, TabContent, Stack } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import SharedPreferences from 'app/core/components/SharedPreferences/SharedPreferences'; @@ -76,30 +76,21 @@ export function UserProfileEditPage({ useMount(() => initUserProfilePage()); - const extensionComponents = useMemo(() => { - const { extensions } = getPluginComponentExtensions({ - extensionPointId: PluginExtensionPoints.UserProfileTab, - }); + const { extensions } = usePluginComponentExtensions({ extensionPointId: PluginExtensionPoints.UserProfileTab }); - return extensions; - }, []); - - const groupedExtensionComponents = extensionComponents.reduce>( - (acc, extension) => { - const { title } = extension; - if (acc[title]) { - acc[title].push(extension); - } else { - acc[title] = [extension]; - } - return acc; - }, - {} - ); + const groupedExtensionComponents = extensions.reduce>((acc, extension) => { + const { title } = extension; + if (acc[title]) { + acc[title].push(extension); + } else { + acc[title] = [extension]; + } + return acc; + }, {}); const convertExtensionComponentTitleToTabId = (title: string) => title.toLowerCase(); - const showTabs = extensionComponents.length > 0; + const showTabs = extensions.length > 0; const tabs: TabInfo[] = [ { id: GENERAL_SETTINGS_TAB, diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryEditor.test.tsx b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryEditor.test.tsx index 14b89c583b7..1f1b89830ec 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryEditor.test.tsx +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryEditor.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import { CoreApp, PluginType } from '@grafana/data'; -import { setPluginExtensionGetter } from '@grafana/runtime'; +import { setPluginExtensionsHook } from '@grafana/runtime'; import { PyroscopeDataSource } from '../datasource'; import { mockFetchPyroscopeDatasourceSettings } from '../datasource.test'; @@ -13,7 +13,7 @@ import { Props, QueryEditor } from './QueryEditor'; describe('QueryEditor', () => { beforeEach(() => { - setPluginExtensionGetter(() => ({ extensions: [] })); // No extensions + setPluginExtensionsHook(() => ({ extensions: [], isLoading: false })); // No extensions mockFetchPyroscopeDatasourceSettings(); }); diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryLinkExtension.test.tsx b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryLinkExtension.test.tsx index 893a23f5b53..dd57142644a 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryLinkExtension.test.tsx +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryLinkExtension.test.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { PluginType, rangeUtil, PluginExtensionLink, PluginExtensionTypes } from '@grafana/data'; -import { getPluginLinkExtensions } from '@grafana/runtime'; +import { usePluginLinkExtensions } from '@grafana/runtime'; import { PyroscopeDataSource } from '../datasource'; import { mockFetchPyroscopeDatasourceSettings } from '../datasource.test'; @@ -15,8 +15,7 @@ const EXTENSION_POINT_ID = 'plugins/grafana-pyroscope-datasource/query-links'; jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), - setPluginExtensionGetter: jest.fn(), - getPluginLinkExtensions: jest.fn(), + usePluginLinkExtensions: jest.fn(), getTemplateSrv: () => { return { replace: (query: string): string => { @@ -26,7 +25,7 @@ jest.mock('@grafana/runtime', () => ({ }, })); -const getPluginLinkExtensionsMock = jest.mocked(getPluginLinkExtensions); +const usePluginLinkExtensionsMock = jest.mocked(usePluginLinkExtensions); const defaultPyroscopeDataSourceSettings = { uid: 'default-pyroscope', @@ -60,12 +59,12 @@ describe('PyroscopeQueryLinkExtensions', () => { resetPyroscopeQueryLinkExtensionsFetches(); mockFetchPyroscopeDatasourceSettings(defaultPyroscopeDataSourceSettings); - getPluginLinkExtensionsMock.mockRestore(); - getPluginLinkExtensionsMock.mockReturnValue({ extensions: [] }); // Unless stated otherwise, no extensions + usePluginLinkExtensionsMock.mockRestore(); + usePluginLinkExtensionsMock.mockReturnValue({ extensions: [], isLoading: false }); // Unless stated otherwise, no extensions }); it('should render if extension present', async () => { - getPluginLinkExtensionsMock.mockReturnValue({ extensions: [createExtension()] }); // Default extension + usePluginLinkExtensionsMock.mockReturnValue({ extensions: [createExtension()], isLoading: false }); // Default extension await act(setup); expect(await screen.findAllByText(EXPECTED_BUTTON_LABEL)).toBeDefined(); diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryLinkExtension.tsx b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryLinkExtension.tsx index 21a8a727ad5..8a6761966df 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryLinkExtension.tsx +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryLinkExtension.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { useAsync } from 'react-use'; import { GrafanaTheme2, QueryEditorProps, TimeRange } from '@grafana/data'; -import { getBackendSrv, getPluginLinkExtensions } from '@grafana/runtime'; +import { getBackendSrv, usePluginLinkExtensions } from '@grafana/runtime'; import { LinkButton, useStyles2 } from '@grafana/ui'; import { PyroscopeDataSource } from '../datasource'; @@ -64,7 +64,7 @@ export function PyroscopeQueryLinkExtensions(props: Props) { datasourceSettings, }; - const { extensions } = getPluginLinkExtensions({ + const { extensions } = usePluginLinkExtensions({ extensionPointId: EXTENSION_POINT_ID, context, }); diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.test.ts b/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.test.ts index b2bc6791e09..6c4720c0600 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.test.ts +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.test.ts @@ -6,7 +6,7 @@ import { PluginType, DataSourceJsonData, } from '@grafana/data'; -import { setPluginExtensionGetter, getBackendSrv, setBackendSrv, getTemplateSrv } from '@grafana/runtime'; +import { setPluginExtensionsHook, getBackendSrv, setBackendSrv, getTemplateSrv } from '@grafana/runtime'; import { defaultPyroscopeQueryType } from './dataquery.gen'; import { normalizeQuery, PyroscopeDataSource } from './datasource'; @@ -50,7 +50,7 @@ describe('Pyroscope data source', () => { let ds: PyroscopeDataSource; beforeEach(() => { mockFetchPyroscopeDatasourceSettings(); - setPluginExtensionGetter(() => ({ extensions: [] })); // No extensions + setPluginExtensionsHook(() => ({ extensions: [], isLoading: false })); // No extensions ds = new PyroscopeDataSource(defaultSettings); }); diff --git a/public/app/plugins/panel/alertlist/UnifiedalertList.test.tsx b/public/app/plugins/panel/alertlist/UnifiedalertList.test.tsx index d4847dd8289..bc8430d453a 100644 --- a/public/app/plugins/panel/alertlist/UnifiedalertList.test.tsx +++ b/public/app/plugins/panel/alertlist/UnifiedalertList.test.tsx @@ -5,7 +5,7 @@ import { Provider } from 'react-redux'; import { byRole, byText } from 'testing-library-selector'; import { FieldConfigSource, getDefaultTimeRange, LoadingState, PanelProps, PluginExtensionTypes } from '@grafana/data'; -import { getPluginLinkExtensions, TimeRangeUpdatedEvent } from '@grafana/runtime'; +import { TimeRangeUpdatedEvent, usePluginLinkExtensions } from '@grafana/runtime'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { mockPromRulesApiResponse } from 'app/features/alerting/unified/mocks/alertRuleApi'; import { mockRulerRulesApiResponse } from 'app/features/alerting/unified/mocks/rulerApi'; @@ -57,12 +57,12 @@ const grafanaRuleMock = { jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), - getPluginLinkExtensions: jest.fn(), + usePluginLinkExtensions: jest.fn(), })); jest.mock('app/features/alerting/unified/api/alertmanager'); const mocks = { - getPluginLinkExtensionsMock: jest.mocked(getPluginLinkExtensions), + usePluginLinkExtensionsMock: jest.mocked(usePluginLinkExtensions), }; const fakeResponse: PromRulesResponse = { @@ -85,7 +85,7 @@ beforeEach(() => { mockRulerRulesApiResponse(server, 'grafana', { 'folder-one': [{ name: 'group1', interval: '20s', rules: [originRule] }], }); - mocks.getPluginLinkExtensionsMock.mockReturnValue({ + mocks.usePluginLinkExtensionsMock.mockReturnValue({ extensions: [ { pluginId: 'grafana-ml-app', @@ -97,6 +97,7 @@ beforeEach(() => { onClick: jest.fn(), }, ], + isLoading: false, }); }); From 0fa983ad8ea4ba9afa54de321e5e2f52df704221 Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Wed, 24 Apr 2024 09:57:34 +0200 Subject: [PATCH 074/222] AuthN: Use typed namespace id inside authn package (#86048) * authn: Use typed namespace id inside package --- pkg/api/login_test.go | 2 +- pkg/api/org_test.go | 2 +- pkg/middleware/auth_test.go | 4 +- pkg/middleware/quota_test.go | 2 +- .../accesscontrol/authorize_in_org_test.go | 2 +- pkg/services/auth/idimpl/service_test.go | 4 +- pkg/services/authn/authnimpl/service.go | 13 +++--- pkg/services/authn/authnimpl/service_test.go | 28 ++++++------ .../authn/authnimpl/sync/oauth_token_sync.go | 7 ++- .../authnimpl/sync/oauth_token_sync_test.go | 10 ++--- pkg/services/authn/authnimpl/sync/org_sync.go | 19 ++++---- .../authn/authnimpl/sync/org_sync_test.go | 20 ++++----- .../authn/authnimpl/sync/rbac_sync.go | 6 +-- .../authn/authnimpl/sync/rbac_sync_test.go | 14 +++--- .../authn/authnimpl/sync/user_sync.go | 2 +- .../authn/authnimpl/sync/user_sync_test.go | 29 +++++-------- pkg/services/authn/clients/api_key.go | 4 +- pkg/services/authn/clients/api_key_test.go | 14 +++--- pkg/services/authn/clients/basic_test.go | 4 +- pkg/services/authn/clients/ext_jwt.go | 14 +++++- pkg/services/authn/clients/ext_jwt_test.go | 4 +- pkg/services/authn/clients/grafana.go | 12 +++--- pkg/services/authn/clients/grafana_test.go | 31 ++++++------- pkg/services/authn/clients/jwt_test.go | 2 - pkg/services/authn/clients/password_test.go | 8 ++-- pkg/services/authn/clients/proxy.go | 2 +- pkg/services/authn/clients/proxy_test.go | 4 +- pkg/services/authn/clients/render.go | 4 +- pkg/services/authn/clients/render_test.go | 4 +- pkg/services/authn/clients/session.go | 2 +- pkg/services/authn/clients/session_test.go | 8 ++-- pkg/services/authn/identity.go | 43 ++++--------------- pkg/services/authn/namespace.go | 38 +++++++++++++--- .../contexthandler/contexthandler_test.go | 4 +- pkg/services/oauthtoken/oauth_token_test.go | 24 +++++------ pkg/services/user/userimpl/verifier.go | 2 +- 36 files changed, 189 insertions(+), 203 deletions(-) diff --git a/pkg/api/login_test.go b/pkg/api/login_test.go index 1d4c725f23c..c0aed770605 100644 --- a/pkg/api/login_test.go +++ b/pkg/api/login_test.go @@ -331,7 +331,7 @@ func TestLoginPostRedirect(t *testing.T) { HooksService: &hooks.HooksService{}, License: &licensing.OSSLicensingService{}, authnService: &authntest.FakeService{ - ExpectedIdentity: &authn.Identity{ID: "user:42", SessionToken: &usertoken.UserToken{}}, + ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:42"), SessionToken: &usertoken.UserToken{}}, }, AuthTokenService: authtest.NewFakeUserAuthTokenService(), Features: featuremgmt.WithFeatures(), diff --git a/pkg/api/org_test.go b/pkg/api/org_test.go index 39acc89cbd2..fcbc7f79bbf 100644 --- a/pkg/api/org_test.go +++ b/pkg/api/org_test.go @@ -266,7 +266,7 @@ func TestAPIEndpoint_GetOrg(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { expectedIdentity := &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), OrgID: 1, Permissions: map[int64]map[string][]string{ 0: accesscontrol.GroupScopesByAction(tt.permissions), diff --git a/pkg/middleware/auth_test.go b/pkg/middleware/auth_test.go index bc45f07ac9c..c5fc39ae508 100644 --- a/pkg/middleware/auth_test.go +++ b/pkg/middleware/auth_test.go @@ -94,7 +94,7 @@ func TestAuth_Middleware(t *testing.T) { desc: "ReqSignedInNoAnonymous should return 200 for authenticated user", path: "/api/secure", authMiddleware: ReqSignedInNoAnonymous, - identity: &authn.Identity{ID: "user:1"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, expecedReached: true, expectedCode: http.StatusOK, }, @@ -102,7 +102,7 @@ func TestAuth_Middleware(t *testing.T) { desc: "snapshot public mode disabled should return 200 for authenticated user", path: "/api/secure", authMiddleware: SnapshotPublicModeOrSignedIn(&setting.Cfg{SnapshotPublicMode: false}), - identity: &authn.Identity{ID: "user:1"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, expecedReached: true, expectedCode: http.StatusOK, }, diff --git a/pkg/middleware/quota_test.go b/pkg/middleware/quota_test.go index 0cbc27da4e2..811871de525 100644 --- a/pkg/middleware/quota_test.go +++ b/pkg/middleware/quota_test.go @@ -52,7 +52,7 @@ func TestMiddlewareQuota(t *testing.T) { t.Run("with user logged in", func(t *testing.T) { setUp := func(sc *scenarioContext) { - sc.withIdentity(&authn.Identity{ID: "user:1", SessionToken: &auth.UserToken{UserId: 12}}) + sc.withIdentity(&authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{UserId: 12}}) } middlewareScenario(t, "global datasource quota reached", func(t *testing.T, sc *scenarioContext) { diff --git a/pkg/services/accesscontrol/authorize_in_org_test.go b/pkg/services/accesscontrol/authorize_in_org_test.go index 81d53dab2b8..c0c91734cba 100644 --- a/pkg/services/accesscontrol/authorize_in_org_test.go +++ b/pkg/services/accesscontrol/authorize_in_org_test.go @@ -186,7 +186,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/endpoint", nil) expectedIdentity := &authn.Identity{ - ID: fmt.Sprintf("user:%v", tc.ctxSignedInUser.UserID), + ID: authn.MustNewNamespaceID(authn.NamespaceUser, tc.ctxSignedInUser.UserID), OrgID: tc.targetOrgId, Permissions: map[int64]map[string][]string{}, } diff --git a/pkg/services/auth/idimpl/service_test.go b/pkg/services/auth/idimpl/service_test.go index 37e89229460..94495f54873 100644 --- a/pkg/services/auth/idimpl/service_test.go +++ b/pkg/services/auth/idimpl/service_test.go @@ -69,7 +69,7 @@ func TestService_SignIdentity(t *testing.T) { featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding), &authntest.FakeService{}, nil, ) - token, err := s.SignIdentity(context.Background(), &authn.Identity{ID: "user:1"}) + token, err := s.SignIdentity(context.Background(), &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}) require.NoError(t, err) require.NotEmpty(t, token) }) @@ -80,7 +80,7 @@ func TestService_SignIdentity(t *testing.T) { featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding), &authntest.FakeService{}, nil, ) - token, err := s.SignIdentity(context.Background(), &authn.Identity{ID: "user:1", AuthenticatedBy: login.AzureADAuthModule}) + token, err := s.SignIdentity(context.Background(), &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), AuthenticatedBy: login.AzureADAuthModule}) require.NoError(t, err) parsed, err := jwt.ParseSigned(token) diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index ad8bb42c15f..bfd4698095a 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -188,14 +188,13 @@ func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (i return nil, err } - namespace, namespaceID := id.GetNamespacedID() // Login is only supported for users - if namespace != authn.NamespaceUser { + if !id.ID.IsNamespace(authn.NamespaceUser) { s.metrics.failedLogin.WithLabelValues(client).Inc() - return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", namespace) + return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", id.ID.Namespace()) } - intId, err := identity.IntIdentifier(namespace, namespaceID) + userID, err := id.ID.ParseInt() if err != nil { return nil, err } @@ -206,7 +205,7 @@ func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (i s.log.FromContext(ctx).Debug("Failed to parse ip from address", "client", c.Name(), "id", id.ID, "addr", addr, "error", err) } - sessionToken, err := s.sessionService.CreateToken(ctx, &user.User{ID: intId}, ip, r.HTTPRequest.UserAgent()) + sessionToken, err := s.sessionService.CreateToken(ctx, &user.User{ID: userID}, ip, r.HTTPRequest.UserAgent()) if err != nil { s.metrics.failedLogin.WithLabelValues(client).Inc() s.log.FromContext(ctx).Error("Failed to create session", "client", client, "id", id.ID, "err", err) @@ -340,7 +339,7 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID a if namespaceID.IsNamespace(authn.NamespaceUser) { return &authn.Identity{ OrgID: orgID, - ID: namespaceID.String(), + ID: namespaceID, ClientParams: authn.ClientParams{ AllowGlobalOrg: true, FetchSyncedUser: true, @@ -350,7 +349,7 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID a if namespaceID.IsNamespace(authn.NamespaceServiceAccount) { return &authn.Identity{ - ID: namespaceID.String(), + ID: namespaceID, OrgID: orgID, ClientParams: authn.ClientParams{ AllowGlobalOrg: true, diff --git a/pkg/services/authn/authnimpl/service_test.go b/pkg/services/authn/authnimpl/service_test.go index 045ebe6accf..c96147eb7eb 100644 --- a/pkg/services/authn/authnimpl/service_test.go +++ b/pkg/services/authn/authnimpl/service_test.go @@ -40,26 +40,26 @@ func TestService_Authenticate(t *testing.T) { { desc: "should succeed with authentication for configured client", clients: []authn.Client{ - &authntest.FakeClient{ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: "user:1"}}, + &authntest.FakeClient{ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}}, }, - expectedIdentity: &authn.Identity{ID: "user:1"}, + expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, }, { desc: "should succeed with authentication for second client when first test fail", clients: []authn.Client{ &authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 1, ExpectedTest: false}, - &authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 2, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: "user:2"}}, + &authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 2, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")}}, }, - expectedIdentity: &authn.Identity{ID: "user:2"}, + expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")}, }, { desc: "should succeed with authentication for third client when error happened in first", clients: []authn.Client{ &authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 2, ExpectedTest: false}, &authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 1, ExpectedTest: true, ExpectedErr: errors.New("some error")}, - &authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: "user:3"}}, + &authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:3")}}, }, - expectedIdentity: &authn.Identity{ID: "user:3"}, + expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:3")}, }, { desc: "should return error when no client could authenticate the request", @@ -214,10 +214,10 @@ func TestService_Login(t *testing.T) { client: "fake", expectedClientOK: true, expectedClientIdentity: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), }, expectedIdentity: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{UserId: 1}, }, }, @@ -230,7 +230,7 @@ func TestService_Login(t *testing.T) { desc: "should not login non user identity", client: "fake", expectedClientOK: true, - expectedClientIdentity: &authn.Identity{ID: "apikey:1"}, + expectedClientIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("api-key:1")}, expectedErr: authn.ErrUnsupportedIdentity, }, } @@ -318,31 +318,31 @@ func TestService_Logout(t *testing.T) { tests := []TestCase{ { desc: "should redirect to default redirect url when identity is not a user", - identity: &authn.Identity{ID: authn.NamespacedID(authn.NamespaceServiceAccount, 1)}, + identity: &authn.Identity{ID: authn.MustNewNamespaceID(authn.NamespaceServiceAccount, 1)}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, }, { desc: "should redirect to default redirect url when no external provider was used to authenticate", - identity: &authn.Identity{ID: authn.NamespacedID(authn.NamespaceUser, 1)}, + identity: &authn.Identity{ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1)}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, expectedTokenRevoked: true, }, { desc: "should redirect to default redirect url when client is not found", - identity: &authn.Identity{ID: authn.NamespacedID(authn.NamespaceUser, 1), AuthenticatedBy: "notfound"}, + identity: &authn.Identity{ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "notfound"}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, expectedTokenRevoked: true, }, { desc: "should redirect to default redirect url when client do not implement logout extension", - identity: &authn.Identity{ID: authn.NamespacedID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"}, + identity: &authn.Identity{ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"}, expectedTokenRevoked: true, }, { desc: "should redirect to client specific url", - identity: &authn.Identity{ID: authn.NamespacedID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"}, + identity: &authn.Identity{ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"}, expectedRedirect: &authn.Redirect{URL: "http://idp.com/logout"}, client: &authntest.MockClient{ NameFunc: func() string { return "auth.client.azuread" }, diff --git a/pkg/services/authn/authnimpl/sync/oauth_token_sync.go b/pkg/services/authn/authnimpl/sync/oauth_token_sync.go index 935b4ec68c9..165613d45df 100644 --- a/pkg/services/authn/authnimpl/sync/oauth_token_sync.go +++ b/pkg/services/authn/authnimpl/sync/oauth_token_sync.go @@ -34,9 +34,8 @@ type OAuthTokenSync struct { } func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error { - namespace, _ := identity.GetNamespacedID() // only perform oauth token check if identity is a user - if namespace != authn.NamespaceUser { + if !identity.ID.IsNamespace(authn.NamespaceUser) { return nil } @@ -50,8 +49,8 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn return nil } - _, err, _ := s.singleflightGroup.Do(identity.ID, func() (interface{}, error) { - s.log.Debug("Singleflight request for OAuth token sync", "key", identity.ID) + _, err, _ := s.singleflightGroup.Do(identity.ID.String(), func() (interface{}, error) { + s.log.Debug("Singleflight request for OAuth token sync", "key", identity.ID.String()) // FIXME: Consider using context.WithoutCancel instead of context.Background after Go 1.21 update updateCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second) diff --git a/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go b/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go index 03db9ff1dd0..16b69dfc071 100644 --- a/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go @@ -41,17 +41,17 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) { tests := []testCase{ { desc: "should skip sync when identity is not a user", - identity: &authn.Identity{ID: "service-account:1"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("service-account:1")}, expectTryRefreshTokenCalled: false, }, { desc: "should skip sync when identity is a user but is not authenticated with session token", - identity: &authn.Identity{ID: "user:1"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, expectTryRefreshTokenCalled: false, }, { desc: "should invalidate access token and session token if token refresh fails", - identity: &authn.Identity{ID: "user:1", SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, expectHasEntryCalled: true, expectedTryRefreshErr: errors.New("some err"), expectTryRefreshTokenCalled: true, @@ -62,7 +62,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) { }, { desc: "should refresh the token successfully", - identity: &authn.Identity{ID: "user:1", SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, expectHasEntryCalled: false, expectTryRefreshTokenCalled: true, expectInvalidateOauthTokensCalled: false, @@ -70,7 +70,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) { }, { desc: "should not invalidate the token if the token has already been refreshed by another request (singleflight)", - identity: &authn.Identity{ID: "user:1", SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, expectHasEntryCalled: true, expectTryRefreshTokenCalled: true, expectInvalidateOauthTokensCalled: false, diff --git a/pkg/services/authn/authnimpl/sync/org_sync.go b/pkg/services/authn/authnimpl/sync/org_sync.go index d8d1146dab4..8492e44de09 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync.go +++ b/pkg/services/authn/authnimpl/sync/org_sync.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" @@ -35,15 +34,14 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a ctxLogger := s.log.FromContext(ctx).New("id", id.ID, "login", id.Login) - namespace, identifier := id.GetNamespacedID() - if namespace != authn.NamespaceUser { - ctxLogger.Warn("Failed to sync org role, invalid namespace for identity", "namespace", namespace) + if !id.ID.IsNamespace(authn.NamespaceUser) { + ctxLogger.Warn("Failed to sync org role, invalid namespace for identity", "namespace", id.ID.Namespace()) return nil } - userID, err := identity.IntIdentifier(namespace, identifier) + userID, err := id.ID.ParseInt() if err != nil { - ctxLogger.Warn("Failed to sync org role, invalid ID for identity", "namespace", namespace, "err", err) + ctxLogger.Warn("Failed to sync org role, invalid ID for identity", "namespace", id.ID.Namespace(), "err", err) return nil } @@ -139,15 +137,14 @@ func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn. ctxLogger := s.log.FromContext(ctx) - namespace, identifier := currentIdentity.GetNamespacedID() - if namespace != identity.NamespaceUser { - ctxLogger.Debug("Skipping default org sync, not a user", "namespace", namespace) + if !currentIdentity.ID.IsNamespace(authn.NamespaceUser) { + ctxLogger.Debug("Skipping default org sync, not a user", "namespace", currentIdentity.ID.Namespace()) return } - userID, err := identity.IntIdentifier(namespace, identifier) + userID, err := currentIdentity.ID.ParseInt() if err != nil { - ctxLogger.Debug("Skipping default org sync, invalid ID for identity", "id", currentIdentity.ID, "namespace", namespace, "err", err) + ctxLogger.Debug("Skipping default org sync, invalid ID for identity", "id", currentIdentity.ID, "namespace", currentIdentity.ID.Namespace(), "err", err) return } diff --git a/pkg/services/authn/authnimpl/sync/org_sync_test.go b/pkg/services/authn/authnimpl/sync/org_sync_test.go index 93d46e8f823..16c94171c4c 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/org_sync_test.go @@ -75,7 +75,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { args: args{ ctx: context.Background(), id: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), Login: "test", Name: "test", Email: "test", @@ -91,7 +91,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { }, }, wantID: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), Login: "test", Name: "test", Email: "test", @@ -137,7 +137,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { { name: "should set default org", defaultOrgSetting: 2, - identity: &authn.Identity{ID: "user:1"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { userService.On("SetUsingOrg", mock.Anything, mock.MatchedBy(func(cmd *user.SetUsingOrgCommand) bool { return cmd.UserID == 1 && cmd.OrgID == 2 @@ -147,7 +147,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { { name: "should skip setting the default org when default org is not set", defaultOrgSetting: -1, - identity: &authn.Identity{ID: "user:1"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, }, { name: "should skip setting the default org when identity is nil", @@ -157,28 +157,28 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { { name: "should skip setting the default org when input err is not nil", defaultOrgSetting: 2, - identity: &authn.Identity{ID: "user:1"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, inputErr: fmt.Errorf("error"), }, { name: "should skip setting the default org when identity is not a user", defaultOrgSetting: 2, - identity: &authn.Identity{ID: "service-account:1"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("service-account:1")}, }, { name: "should skip setting the default org when user id is not valid", defaultOrgSetting: 2, - identity: &authn.Identity{ID: "user:invalid"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:invalid")}, }, { name: "should skip setting the default org when user is not allowed to use the configured default org", defaultOrgSetting: 3, - identity: &authn.Identity{ID: "user:1"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, }, { name: "should skip setting the default org when validateUsingOrg returns error", defaultOrgSetting: 2, - identity: &authn.Identity{ID: "user:1"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { orgService.ExpectedError = fmt.Errorf("error") }, @@ -186,7 +186,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { { name: "should skip the hook when the user org update was unsuccessful", defaultOrgSetting: 2, - identity: &authn.Identity{ID: "user:1"}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { userService.On("SetUsingOrg", mock.Anything, mock.Anything).Return(fmt.Errorf("error")) }, diff --git a/pkg/services/authn/authnimpl/sync/rbac_sync.go b/pkg/services/authn/authnimpl/sync/rbac_sync.go index 121c2dcadbc..df10c2414ae 100644 --- a/pkg/services/authn/authnimpl/sync/rbac_sync.go +++ b/pkg/services/authn/authnimpl/sync/rbac_sync.go @@ -6,7 +6,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -98,13 +97,12 @@ func (s *RBACSync) SyncCloudRoles(ctx context.Context, ident *authn.Identity, r return nil } - namespace, id := ident.GetNamespacedID() - if namespace != authn.NamespaceUser { + if !ident.ID.IsNamespace(authn.NamespaceUser) { s.log.FromContext(ctx).Debug("Skip syncing cloud role", "id", ident.ID) return nil } - userID, err := identity.IntIdentifier(namespace, id) + userID, err := ident.ID.ParseInt() if err != nil { return err } diff --git a/pkg/services/authn/authnimpl/sync/rbac_sync_test.go b/pkg/services/authn/authnimpl/sync/rbac_sync_test.go index 27436998f4d..202dd58cadf 100644 --- a/pkg/services/authn/authnimpl/sync/rbac_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/rbac_sync_test.go @@ -24,14 +24,14 @@ func TestRBACSync_SyncPermission(t *testing.T) { testCases := []testCase{ { name: "enriches the identity successfully when SyncPermissions is true", - identity: &authn.Identity{ID: "user:2", OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}}, expectedPermissions: []accesscontrol.Permission{ {Action: accesscontrol.ActionUsersRead}, }, }, { name: "does not load the permissions when SyncPermissions is false", - identity: &authn.Identity{ID: "user:2", OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}}, expectedPermissions: []accesscontrol.Permission{ {Action: accesscontrol.ActionUsersRead}, }, @@ -65,7 +65,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should call sync when authenticated with grafana com and has viewer role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceUser, 1), + ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, }, @@ -76,7 +76,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should call sync when authenticated with grafana com and has editor role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceUser, 1), + ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleEditor}, }, @@ -87,7 +87,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should call sync when authenticated with grafana com and has admin role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceUser, 1), + ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, }, @@ -98,7 +98,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should not call sync when authenticated with grafana com and has invalid role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceUser, 1), + ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleType("something else")}, }, @@ -109,7 +109,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should not call sync when not authenticated with grafana com", module: login.LDAPAuthModule, identity: &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceUser, 1), + ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, }, diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index fb6d4d006e3..eb09115d580 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -394,7 +394,7 @@ func (s *UserSync) lookupByOneOf(ctx context.Context, params login.UserLookupPar // syncUserToIdentity syncs a user to an identity. // This is used to update the identity with the latest user information. func syncUserToIdentity(usr *user.User, id *authn.Identity) { - id.ID = authn.NamespacedID(authn.NamespaceUser, usr.ID) + id.ID = authn.NewNamespaceIDUnchecked(authn.NamespaceUser, usr.ID) id.Login = usr.Login id.Email = usr.Email id.Name = usr.Name diff --git a/pkg/services/authn/authnimpl/sync/user_sync_test.go b/pkg/services/authn/authnimpl/sync/user_sync_test.go index f34b16c168d..4e861a6ad1f 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/user_sync_test.go @@ -110,7 +110,6 @@ func TestUserSync_SyncUserHook(t *testing.T) { args: args{ ctx: context.Background(), id: &authn.Identity{ - ID: "", Login: "test", Name: "test", Email: "test", @@ -124,7 +123,6 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: "", Login: "test", Name: "test", Email: "test", @@ -146,7 +144,6 @@ func TestUserSync_SyncUserHook(t *testing.T) { args: args{ ctx: context.Background(), id: &authn.Identity{ - ID: "", Login: "test", Name: "test", Email: "test", @@ -161,7 +158,7 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), Login: "test", Name: "test", Email: "test", @@ -185,7 +182,6 @@ func TestUserSync_SyncUserHook(t *testing.T) { args: args{ ctx: context.Background(), id: &authn.Identity{ - ID: "", Login: "test", Name: "test", Email: "test", @@ -200,7 +196,7 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), Login: "test", Name: "test", Email: "test", @@ -224,7 +220,6 @@ func TestUserSync_SyncUserHook(t *testing.T) { args: args{ ctx: context.Background(), id: &authn.Identity{ - ID: "", AuthID: "2032", AuthenticatedBy: "oauth", Login: "test", @@ -241,7 +236,7 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), AuthID: "2032", AuthenticatedBy: "oauth", Login: "test", @@ -267,7 +262,6 @@ func TestUserSync_SyncUserHook(t *testing.T) { args: args{ ctx: context.Background(), id: &authn.Identity{ - ID: "", Login: "test", Name: "test", Email: "test", @@ -294,7 +288,6 @@ func TestUserSync_SyncUserHook(t *testing.T) { args: args{ ctx: context.Background(), id: &authn.Identity{ - ID: "", Login: "test_create", Name: "test_create", IsGrafanaAdmin: ptrBool(true), @@ -314,7 +307,7 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: "user:2", + ID: authn.MustParseNamespaceID("user:2"), Login: "test_create", Name: "test_create", Email: "test_create", @@ -342,7 +335,6 @@ func TestUserSync_SyncUserHook(t *testing.T) { args: args{ ctx: context.Background(), id: &authn.Identity{ - ID: "", Login: "test_mod", Name: "test_mod", Email: "test_mod", @@ -360,7 +352,7 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: "user:3", + ID: authn.MustParseNamespaceID("user:3"), Login: "test_mod", Name: "test_mod", Email: "test_mod", @@ -386,7 +378,6 @@ func TestUserSync_SyncUserHook(t *testing.T) { args: args{ ctx: context.Background(), id: &authn.Identity{ - ID: "", Login: "test", Name: "test", Email: "test_mod@test.com", @@ -405,7 +396,7 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: "user:3", + ID: authn.MustParseNamespaceID("user:3"), Login: "test", Name: "test", Email: "test_mod@test.com", @@ -455,7 +446,7 @@ func TestUserSync_FetchSyncedUserHook(t *testing.T) { { desc: "should skip hook when identity is not a user", req: &authn.Request{}, - identity: &authn.Identity{ID: "apikey:1", ClientParams: authn.ClientParams{FetchSyncedUser: true}}, + identity: &authn.Identity{ID: authn.MustParseNamespaceID("api-key:1"), ClientParams: authn.ClientParams{FetchSyncedUser: true}}, }, } @@ -479,7 +470,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) { { desc: "should skip if correct flag is not set", identity: &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceUser, 1), + ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1), IsDisabled: true, ClientParams: authn.ClientParams{EnableUser: false}, }, @@ -488,7 +479,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) { { desc: "should skip if identity is not a user", identity: &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceAPIKey, 1), + ID: authn.MustNewNamespaceID(authn.NamespaceAPIKey, 1), IsDisabled: true, ClientParams: authn.ClientParams{EnableUser: true}, }, @@ -497,7 +488,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) { { desc: "should enabled disabled user", identity: &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceUser, 1), + ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1), IsDisabled: true, ClientParams: authn.ClientParams{EnableUser: true}, }, diff --git a/pkg/services/authn/clients/api_key.go b/pkg/services/authn/clients/api_key.go index c2679470fc7..4b36922cf99 100644 --- a/pkg/services/authn/clients/api_key.go +++ b/pkg/services/authn/clients/api_key.go @@ -256,7 +256,7 @@ func validateApiKey(orgID int64, key *apikey.APIKey) error { func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity { return &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceAPIKey, key.ID), + ID: authn.NewNamespaceIDUnchecked(authn.NamespaceAPIKey, key.ID), OrgID: key.OrgID, OrgRoles: map[int64]org.RoleType{key.OrgID: key.Role}, ClientParams: authn.ClientParams{SyncPermissions: true}, @@ -266,7 +266,7 @@ func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity { func newServiceAccountIdentity(key *apikey.APIKey) *authn.Identity { return &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceServiceAccount, *key.ServiceAccountId), + ID: authn.NewNamespaceIDUnchecked(authn.NamespaceServiceAccount, *key.ServiceAccountId), OrgID: key.OrgID, AuthenticatedBy: login.APIKeyAuthModule, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, diff --git a/pkg/services/authn/clients/api_key_test.go b/pkg/services/authn/clients/api_key_test.go index 2905503a633..c67c11b6cf0 100644 --- a/pkg/services/authn/clients/api_key_test.go +++ b/pkg/services/authn/clients/api_key_test.go @@ -47,7 +47,7 @@ func TestAPIKey_Authenticate(t *testing.T) { Role: org.RoleAdmin, }, expectedIdentity: &authn.Identity{ - ID: "api-key:1", + ID: authn.MustParseNamespaceID("api-key:1"), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, ClientParams: authn.ClientParams{ @@ -70,7 +70,7 @@ func TestAPIKey_Authenticate(t *testing.T) { ServiceAccountId: intPtr(1), }, expectedIdentity: &authn.Identity{ - ID: "service-account:1", + ID: authn.MustParseNamespaceID("service-account:1"), OrgID: 1, ClientParams: authn.ClientParams{ FetchSyncedUser: true, @@ -205,7 +205,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) { ServiceAccountId: intPtr(1), }, expectedIdentity: &authn.Identity{ - ID: "service-account:1", + ID: authn.MustParseNamespaceID("service-account:1"), OrgID: 1, Name: "test", AuthenticatedBy: login.APIKeyAuthModule, @@ -221,7 +221,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) { Key: hash, }, expectedIdentity: &authn.Identity{ - ID: "api-key:2", + ID: authn.MustParseNamespaceID("api-key:2"), OrgID: 1, Name: "test", AuthenticatedBy: login.APIKeyAuthModule, @@ -237,7 +237,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) { Key: hash, }, expectedIdentity: &authn.Identity{ - ID: "user:2", + ID: authn.MustParseNamespaceID("user:2"), OrgID: 1, Name: "test", AuthenticatedBy: login.APIKeyAuthModule, @@ -253,7 +253,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) { Key: hash, }, expectedIdentity: &authn.Identity{ - ID: "service-account:2", + ID: authn.MustParseNamespaceID("service-account:2"), OrgID: 1, Name: "test", AuthenticatedBy: login.APIKeyAuthModule, @@ -351,7 +351,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) { expectedIdenity: &authn.Identity{ OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleEditor}, - ID: "api-key:1", + ID: authn.MustParseNamespaceID("api-key:1"), AuthenticatedBy: login.APIKeyAuthModule, ClientParams: authn.ClientParams{SyncPermissions: true}, }, diff --git a/pkg/services/authn/clients/basic_test.go b/pkg/services/authn/clients/basic_test.go index fbf2a96a2d7..f92f29a3144 100644 --- a/pkg/services/authn/clients/basic_test.go +++ b/pkg/services/authn/clients/basic_test.go @@ -24,8 +24,8 @@ func TestBasic_Authenticate(t *testing.T) { { desc: "should success when password client return identity", req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{authorizationHeaderName: {encodeBasicAuth("user", "password")}}}}, - client: authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: "user:1"}}, - expectedIdentity: &authn.Identity{ID: "user:1"}, + client: authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}}, + expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, }, { desc: "should fail when basic auth header could not be decoded", diff --git a/pkg/services/authn/clients/ext_jwt.go b/pkg/services/authn/clients/ext_jwt.go index 303b14d9cad..446a7b0ee05 100644 --- a/pkg/services/authn/clients/ext_jwt.go +++ b/pkg/services/authn/clients/ext_jwt.go @@ -109,8 +109,13 @@ func (s *ExtendedJWT) authenticateAsUser(idTokenClaims, return nil, errJWTInvalid.Errorf("Failed to parse sub: %w", err) } + id, err := authn.ParseNamespaceID(idTokenClaims.Subject) + if err != nil { + return nil, err + } + return &authn.Identity{ - ID: idTokenClaims.Subject, + ID: id, OrgID: s.getDefaultOrgID(), AuthenticatedBy: login.ExtendedJWTModule, AuthID: accessTokenClaims.Subject, @@ -129,8 +134,13 @@ func (s *ExtendedJWT) authenticateAsService(claims *ExtendedJWTClaims) (*authn.I return nil, errJWTInvalid.Errorf("Failed to parse sub: %s", "invalid subject format") } + id, err := authn.ParseNamespaceID(claims.Subject) + if err != nil { + return nil, err + } + return &authn.Identity{ - ID: claims.Subject, + ID: id, OrgID: s.getDefaultOrgID(), AuthenticatedBy: login.ExtendedJWTModule, AuthID: claims.Subject, diff --git a/pkg/services/authn/clients/ext_jwt_test.go b/pkg/services/authn/clients/ext_jwt_test.go index fe8c1135897..9bbba9a684c 100644 --- a/pkg/services/authn/clients/ext_jwt_test.go +++ b/pkg/services/authn/clients/ext_jwt_test.go @@ -166,7 +166,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { orgID: 1, want: &authn.Identity{OrgID: 1, OrgName: "", OrgRoles: map[int64]roletype.RoleType(nil), - ID: "access-policy:this-uid", Login: "", Name: "", Email: "", + ID: authn.MustParseNamespaceID("access-policy:this-uid"), Login: "", Name: "", Email: "", IsGrafanaAdmin: (*bool)(nil), AuthenticatedBy: "extendedjwt", AuthID: "access-policy:this-uid", IsDisabled: false, HelpFlags1: 0x0, LastSeenAt: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), @@ -196,7 +196,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { } }, want: &authn.Identity{OrgID: 1, OrgName: "", - OrgRoles: map[int64]roletype.RoleType(nil), ID: "user:2", + OrgRoles: map[int64]roletype.RoleType(nil), ID: authn.MustParseNamespaceID("user:2"), Login: "", Name: "", Email: "", IsGrafanaAdmin: (*bool)(nil), AuthenticatedBy: "extendedjwt", AuthID: "access-policy:this-uid", IsDisabled: false, HelpFlags1: 0x0, diff --git a/pkg/services/authn/clients/grafana.go b/pkg/services/authn/clients/grafana.go index 10e33008600..e95fb2f5828 100644 --- a/pkg/services/authn/clients/grafana.go +++ b/pkg/services/authn/clients/grafana.go @@ -104,12 +104,12 @@ func (c *Grafana) AuthenticatePassword(ctx context.Context, r *authn.Request, us return nil, errInvalidPassword.Errorf("invalid password") } - signedInUser, err := c.userService.GetSignedInUserWithCacheCtx(ctx, &user.GetSignedInUserQuery{OrgID: r.OrgID, UserID: usr.ID}) - if err != nil { - return nil, err - } - - return authn.IdentityFromSignedInUser(authn.NamespacedID(authn.NamespaceUser, signedInUser.UserID), signedInUser, authn.ClientParams{SyncPermissions: true}, login.PasswordAuthModule), nil + return &authn.Identity{ + ID: authn.NewNamespaceIDUnchecked(authn.NamespaceUser, usr.ID), + OrgID: r.OrgID, + ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, + AuthenticatedBy: login.PasswordAuthModule, + }, nil } func comparePassword(password, salt, hash string) bool { diff --git a/pkg/services/authn/clients/grafana_test.go b/pkg/services/authn/clients/grafana_test.go index b2cb45154ec..d77afb239a2 100644 --- a/pkg/services/authn/clients/grafana_test.go +++ b/pkg/services/authn/clients/grafana_test.go @@ -125,29 +125,25 @@ func TestGrafana_AuthenticateProxy(t *testing.T) { func TestGrafana_AuthenticatePassword(t *testing.T) { type testCase struct { - desc string - username string - password string - findUser bool - expectedErr error - expectedIdentity *authn.Identity - expectedSignedInUser *user.SignedInUser + desc string + username string + password string + findUser bool + expectedErr error + expectedIdentity *authn.Identity } tests := []testCase{ { - desc: "should successfully authenticate user with correct password", - username: "user", - password: "password", - findUser: true, - expectedSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, OrgRole: "Viewer"}, + desc: "should successfully authenticate user with correct password", + username: "user", + password: "password", + findUser: true, expectedIdentity: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), OrgID: 1, - OrgRoles: map[int64]org.RoleType{1: "Viewer"}, - IsGrafanaAdmin: boolPtr(false), - ClientParams: authn.ClientParams{SyncPermissions: true}, AuthenticatedBy: login.PasswordAuthModule, + ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, }, }, { @@ -169,8 +165,7 @@ func TestGrafana_AuthenticatePassword(t *testing.T) { t.Run(tt.desc, func(t *testing.T) { hashed, _ := util.EncodePassword("password", "salt") userService := &usertest.FakeUserService{ - ExpectedSignedInUser: tt.expectedSignedInUser, - ExpectedUser: &user.User{Password: user.Password(hashed), Salt: "salt"}, + ExpectedUser: &user.User{ID: 1, Password: user.Password(hashed), Salt: "salt"}, } if !tt.findUser { diff --git a/pkg/services/authn/clients/jwt_test.go b/pkg/services/authn/clients/jwt_test.go index f5d0dbd09ad..538ffb26e65 100644 --- a/pkg/services/authn/clients/jwt_test.go +++ b/pkg/services/authn/clients/jwt_test.go @@ -40,7 +40,6 @@ func TestAuthenticateJWT(t *testing.T) { OrgName: "", OrgRoles: map[int64]roletype.RoleType{1: roletype.RoleAdmin}, Groups: []string{"foo", "bar"}, - ID: "", Login: "eai-doe", Name: "Eai Doe", Email: "eai.doe@cor.po", @@ -92,7 +91,6 @@ func TestAuthenticateJWT(t *testing.T) { OrgID: 0, OrgName: "", OrgRoles: map[int64]roletype.RoleType{1: roletype.RoleAdmin}, - ID: "", Login: "eai-doe", Groups: []string{}, Name: "Eai Doe", diff --git a/pkg/services/authn/clients/password_test.go b/pkg/services/authn/clients/password_test.go index dda54325146..76d9d87dc00 100644 --- a/pkg/services/authn/clients/password_test.go +++ b/pkg/services/authn/clients/password_test.go @@ -29,16 +29,16 @@ func TestPassword_AuthenticatePassword(t *testing.T) { username: "test", password: "test", req: &authn.Request{}, - clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: "user:1"}}}, - expectedIdentity: &authn.Identity{ID: "user:1"}, + clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}}}, + expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, }, { desc: "should success when found in second client", username: "test", password: "test", req: &authn.Request{}, - clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedErr: errIdentityNotFound}, authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: "user:2"}}}, - expectedIdentity: &authn.Identity{ID: "user:2"}, + clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedErr: errIdentityNotFound}, authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")}}}, + expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")}, }, { desc: "should fail for empty password", diff --git a/pkg/services/authn/clients/proxy.go b/pkg/services/authn/clients/proxy.go index 157af7ee0af..bbe96491533 100644 --- a/pkg/services/authn/clients/proxy.go +++ b/pkg/services/authn/clients/proxy.go @@ -125,7 +125,7 @@ func (c *Proxy) retrieveIDFromCache(ctx context.Context, cacheKey string, r *aut } return &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceUser, uid), + ID: authn.NewNamespaceIDUnchecked(authn.NamespaceUser, uid), OrgID: r.OrgID, // FIXME: This does not match the actual auth module used, but should not have any impact // Maybe caching the auth module used with the user ID would be a good idea diff --git a/pkg/services/authn/clients/proxy_test.go b/pkg/services/authn/clients/proxy_test.go index 1f980122476..d55bd68484e 100644 --- a/pkg/services/authn/clients/proxy_test.go +++ b/pkg/services/authn/clients/proxy_test.go @@ -202,8 +202,8 @@ func TestProxy_Hook(t *testing.T) { proxyFieldRole: "X-Role", } cache := &fakeCache{data: make(map[string][]byte)} - userId := 1 - userID := fmt.Sprintf("%s:%d", authn.NamespaceUser, userId) + userId := int64(1) + userID := authn.MustNewNamespaceID(authn.NamespaceUser, userId) // withRole creates a test case for a user with a specific role. withRole := func(role string) func(t *testing.T) { diff --git a/pkg/services/authn/clients/render.go b/pkg/services/authn/clients/render.go index 0a7b692029a..39f6922d258 100644 --- a/pkg/services/authn/clients/render.go +++ b/pkg/services/authn/clients/render.go @@ -42,7 +42,7 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide if renderUsr.UserID <= 0 { return &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceRenderService, 0), + ID: authn.NewNamespaceIDUnchecked(authn.NamespaceRenderService, 0), OrgID: renderUsr.OrgID, OrgRoles: map[int64]org.RoleType{renderUsr.OrgID: org.RoleType(renderUsr.OrgRole)}, ClientParams: authn.ClientParams{SyncPermissions: true}, @@ -52,7 +52,7 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide } return &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceUser, renderUsr.UserID), + ID: authn.NewNamespaceIDUnchecked(authn.NamespaceUser, renderUsr.UserID), LastSeenAt: time.Now(), AuthenticatedBy: login.RenderModule, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, diff --git a/pkg/services/authn/clients/render_test.go b/pkg/services/authn/clients/render_test.go index 24051db326f..8e815de266e 100644 --- a/pkg/services/authn/clients/render_test.go +++ b/pkg/services/authn/clients/render_test.go @@ -35,7 +35,7 @@ func TestRender_Authenticate(t *testing.T) { }, }, expectedIdentity: &authn.Identity{ - ID: "render:0", + ID: authn.MustParseNamespaceID("render:0"), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, AuthenticatedBy: login.RenderModule, @@ -56,7 +56,7 @@ func TestRender_Authenticate(t *testing.T) { }, }, expectedIdentity: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), AuthenticatedBy: login.RenderModule, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, }, diff --git a/pkg/services/authn/clients/session.go b/pkg/services/authn/clients/session.go index 02ec093b74e..dc928107ba5 100644 --- a/pkg/services/authn/clients/session.go +++ b/pkg/services/authn/clients/session.go @@ -57,7 +57,7 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id } ident := &authn.Identity{ - ID: authn.NamespacedID(authn.NamespaceUser, token.UserId), + ID: authn.NewNamespaceIDUnchecked(authn.NamespaceUser, token.UserId), SessionToken: token, ClientParams: authn.ClientParams{ FetchSyncedUser: true, diff --git a/pkg/services/authn/clients/session_test.go b/pkg/services/authn/clients/session_test.go index 0315083ea5b..c8fbc329277 100644 --- a/pkg/services/authn/clients/session_test.go +++ b/pkg/services/authn/clients/session_test.go @@ -96,7 +96,7 @@ func TestSession_Authenticate(t *testing.T) { }, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, wantID: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), SessionToken: validToken, ClientParams: authn.ClientParams{ SyncPermissions: true, @@ -129,7 +129,7 @@ func TestSession_Authenticate(t *testing.T) { }, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, wantID: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), SessionToken: validToken, ClientParams: authn.ClientParams{ SyncPermissions: true, @@ -148,7 +148,7 @@ func TestSession_Authenticate(t *testing.T) { }, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, wantID: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), AuthID: "1", AuthenticatedBy: "oauth_azuread", SessionToken: validToken, @@ -170,7 +170,7 @@ func TestSession_Authenticate(t *testing.T) { }, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, wantID: &authn.Identity{ - ID: "user:1", + ID: authn.MustParseNamespaceID("user:1"), SessionToken: validToken, ClientParams: authn.ClientParams{ diff --git a/pkg/services/authn/identity.go b/pkg/services/authn/identity.go index 59b23faefbb..3f47c084c24 100644 --- a/pkg/services/authn/identity.go +++ b/pkg/services/authn/identity.go @@ -3,7 +3,6 @@ package authn import ( "fmt" "strconv" - "strings" "time" "golang.org/x/oauth2" @@ -21,17 +20,17 @@ const GlobalOrgID = int64(0) var _ identity.Requester = (*Identity)(nil) type Identity struct { + // ID is the unique identifier for the entity in the Grafana database. + // It is in the format : where namespace is one of the + // Namespace* constants. For example, "user:1" or "api-key:1". + // If the entity is not found in the DB or this entity is non-persistent, this field will be empty. + ID NamespaceID // OrgID is the active organization for the entity. OrgID int64 // OrgName is the name of the active organization. OrgName string // OrgRoles is the list of organizations the entity is a member of and their roles. OrgRoles map[int64]org.RoleType - // ID is the unique identifier for the entity in the Grafana database. - // It is in the format : where namespace is one of the - // Namespace* constants. For example, "user:1" or "api-key:1". - // If the entity is not found in the DB or this entity is non-persistent, this field will be empty. - ID string // Login is the shorthand identifier of the entity. Should be unique. Login string // Name is the display name of the entity. It is not guaranteed to be unique. @@ -74,15 +73,11 @@ type Identity struct { } func (i *Identity) GetID() string { - return i.ID + return i.ID.String() } func (i *Identity) GetNamespacedID() (namespace string, identifier string) { - split := strings.Split(i.GetID(), ":") - if len(split) != 2 { - return "", "" - } - return split[0], split[1] + return i.ID.Namespace(), i.ID.ID() } func (i *Identity) GetAuthID() string { @@ -224,7 +219,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser { Teams: i.Teams, Permissions: i.Permissions, IDToken: i.IDToken, - NamespacedID: i.ID, + NamespacedID: i.ID.String(), } if namespace == NamespaceAPIKey { @@ -263,25 +258,3 @@ func (i *Identity) ExternalUserInfo() login.ExternalUserInfo { IsDisabled: i.IsDisabled, } } - -// IdentityFromSignedInUser creates an identity from a SignedInUser. -func IdentityFromSignedInUser(id string, usr *user.SignedInUser, params ClientParams, authenticatedBy string) *Identity { - return &Identity{ - ID: id, - OrgID: usr.OrgID, - OrgName: usr.OrgName, - OrgRoles: map[int64]org.RoleType{usr.OrgID: usr.OrgRole}, - Login: usr.Login, - Name: usr.Name, - Email: usr.Email, - AuthenticatedBy: authenticatedBy, - IsGrafanaAdmin: &usr.IsGrafanaAdmin, - IsDisabled: usr.IsDisabled, - HelpFlags1: usr.HelpFlags1, - LastSeenAt: usr.LastSeenAt, - Teams: usr.Teams, - ClientParams: params, - Permissions: usr.Permissions, - IDToken: usr.IDToken, - } -} diff --git a/pkg/services/authn/namespace.go b/pkg/services/authn/namespace.go index c46da890e86..a5324561963 100644 --- a/pkg/services/authn/namespace.go +++ b/pkg/services/authn/namespace.go @@ -15,9 +15,10 @@ const ( NamespaceAnonymous = identity.NamespaceAnonymous NamespaceRenderService = identity.NamespaceRenderService NamespaceAccessPolicy = identity.NamespaceAccessPolicy - AnonymousNamespaceID = NamespaceAnonymous + ":0" ) +var AnonymousNamespaceID = MustNewNamespaceID(NamespaceAnonymous, 0) + var namespaceLookup = map[string]struct{}{ NamespaceUser: {}, NamespaceAPIKey: {}, @@ -27,11 +28,6 @@ var namespaceLookup = map[string]struct{}{ NamespaceAccessPolicy: {}, } -// NamespacedID builds a namespaced ID from a namespace and an ID. -func NamespacedID(namespace string, id int64) string { - return fmt.Sprintf("%s:%d", namespace, id) -} - func ParseNamespaceID(str string) (NamespaceID, error) { var namespaceID NamespaceID @@ -62,6 +58,36 @@ func MustParseNamespaceID(str string) NamespaceID { return namespaceID } +// NewNamespaceID creates a new NamespaceID, will fail for invalid namespace. +func NewNamespaceID(namespace string, id int64) (NamespaceID, error) { + var namespaceID NamespaceID + if _, ok := namespaceLookup[namespace]; !ok { + return namespaceID, ErrInvalidNamepsaceID.Errorf("got invalid namespace %s", namespace) + } + namespaceID.id = strconv.FormatInt(id, 10) + namespaceID.namespace = namespace + return namespaceID, nil +} + +// MustNewNamespaceID creates a new NamespaceID, will panic for invalid namespace. +// Sutable to use in tests or when we can garantuee that we pass a correct format. +func MustNewNamespaceID(namespace string, id int64) NamespaceID { + namespaceID, err := NewNamespaceID(namespace, id) + if err != nil { + panic(err) + } + return namespaceID +} + +// NewNamespaceIDUnchecked creates a new NamespaceID without checking if namespace is valid. +// It us up to the caller to ensure that namespace is valid. +func NewNamespaceIDUnchecked(namespace string, id int64) NamespaceID { + return NamespaceID{ + id: strconv.FormatInt(id, 10), + namespace: namespace, + } +} + // FIXME: use this instead of encoded string through the codebase type NamespaceID struct { id string diff --git a/pkg/services/contexthandler/contexthandler_test.go b/pkg/services/contexthandler/contexthandler_test.go index f7c46f7208c..3bdede4be3b 100644 --- a/pkg/services/contexthandler/contexthandler_test.go +++ b/pkg/services/contexthandler/contexthandler_test.go @@ -44,7 +44,7 @@ func TestContextHandler(t *testing.T) { }) t.Run("should set identity on successful authentication", func(t *testing.T) { - identity := &authn.Identity{ID: authn.NamespacedID(authn.NamespaceUser, 1), OrgID: 1} + identity := &authn.Identity{ID: authn.MustNewNamespaceID(authn.NamespaceUser, 1), OrgID: 1} handler := contexthandler.ProvideService( setting.NewCfg(), tracing.InitializeTracerForTest(), @@ -150,7 +150,7 @@ func TestContextHandler(t *testing.T) { cfg, tracing.InitializeTracerForTest(), featuremgmt.WithFeatures(), - &authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: id}}, + &authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID(id)}}, ) server := webtest.NewServer(t, routing.NewRouteRegister()) diff --git a/pkg/services/oauthtoken/oauth_token_test.go b/pkg/services/oauthtoken/oauth_token_test.go index ebcbb76fb3f..873a25cc9ce 100644 --- a/pkg/services/oauthtoken/oauth_token_test.go +++ b/pkg/services/oauthtoken/oauth_token_test.go @@ -174,13 +174,13 @@ func TestService_TryTokenRefresh(t *testing.T) { { desc: "should skip sync when identity is not a user", setup: func(env *environment) { - env.identity = &authn.Identity{ID: "service-account:1"} + env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("service-account:1")} }, }, { desc: "should skip token refresh and return nil if namespace and id cannot be converted to user ID", setup: func(env *environment) { - env.identity = &authn.Identity{ID: "user:invalidIdentifierFormat"} + env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:invalidIdentifierFormat")} }, }, { @@ -203,28 +203,28 @@ func TestService_TryTokenRefresh(t *testing.T) { env.identity = &authn.Identity{ AuthenticatedBy: login.GenericOAuthModule, - ID: "user:1234", + ID: authn.MustParseNamespaceID("user:1234"), } }, }, { desc: "should skip token refresh if the expiration check has already been cached", setup: func(env *environment) { - env.identity = &authn.Identity{ID: "user:1234"} + env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.cache.Set("oauth-refresh-token-1234", true, 1*time.Minute) }, }, { desc: "should skip token refresh if there's an unexpected error while looking up the user oauth entry, additionally, no error should be returned", setup: func(env *environment) { - env.identity = &authn.Identity{ID: "user:1234"} + env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.authInfoService.ExpectedError = errors.New("some error") }, }, { desc: "should skip token refresh if the user doesn't have an oauth entry", setup: func(env *environment) { - env.identity = &authn.Identity{ID: "user:1234"} + env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.authInfoService.ExpectedUserAuth = &login.UserAuth{ AuthModule: login.SAMLAuthModule, } @@ -233,7 +233,7 @@ func TestService_TryTokenRefresh(t *testing.T) { { desc: "should do token refresh if access token or id token have not expired yet", setup: func(env *environment) { - env.identity = &authn.Identity{ID: "user:1234"} + env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.authInfoService.ExpectedUserAuth = &login.UserAuth{ AuthModule: login.GenericOAuthModule, } @@ -242,7 +242,7 @@ func TestService_TryTokenRefresh(t *testing.T) { { desc: "should skip token refresh when no oauth provider was found", setup: func(env *environment) { - env.identity = &authn.Identity{ID: "user:1234"} + env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.authInfoService.ExpectedUserAuth = &login.UserAuth{ AuthModule: login.GenericOAuthModule, OAuthIdToken: EXPIRED_JWT, @@ -252,7 +252,7 @@ func TestService_TryTokenRefresh(t *testing.T) { { desc: "should skip token refresh when oauth provider token handling is disabled (UseRefreshToken is false)", setup: func(env *environment) { - env.identity = &authn.Identity{ID: "user:1234"} + env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.authInfoService.ExpectedUserAuth = &login.UserAuth{ AuthModule: login.GenericOAuthModule, OAuthIdToken: EXPIRED_JWT, @@ -265,7 +265,7 @@ func TestService_TryTokenRefresh(t *testing.T) { { desc: "should skip token refresh when there is no refresh token", setup: func(env *environment) { - env.identity = &authn.Identity{ID: "user:1234"} + env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.authInfoService.ExpectedUserAuth = &login.UserAuth{ AuthModule: login.GenericOAuthModule, OAuthIdToken: EXPIRED_JWT, @@ -285,7 +285,7 @@ func TestService_TryTokenRefresh(t *testing.T) { Expiry: time.Now().Add(-time.Hour), TokenType: "Bearer", } - env.identity = &authn.Identity{ID: "user:1234"} + env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ UseRefreshToken: true, } @@ -310,7 +310,7 @@ func TestService_TryTokenRefresh(t *testing.T) { Expiry: time.Now().Add(time.Hour), TokenType: "Bearer", } - env.identity = &authn.Identity{ID: "user:1234"} + env.identity = &authn.Identity{ID: authn.MustParseNamespaceID("user:1234")} env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ UseRefreshToken: true, } diff --git a/pkg/services/user/userimpl/verifier.go b/pkg/services/user/userimpl/verifier.go index 3017a9b6189..e4b62fa9d61 100644 --- a/pkg/services/user/userimpl/verifier.go +++ b/pkg/services/user/userimpl/verifier.go @@ -152,6 +152,6 @@ func (s *Verifier) Complete(ctx context.Context, cmd user.CompleteEmailVerifyCom // remove the current token, so a new one can be generated with correct values. return s.is.RemoveIDToken( ctx, - &user.SignedInUser{UserID: usr.ID, OrgID: usr.OrgID, NamespacedID: authn.NamespacedID(authn.NamespaceUser, usr.ID)}, + &authn.Identity{ID: authn.NewNamespaceIDUnchecked(authn.NamespaceUser, usr.ID), OrgID: usr.OrgID}, ) } From a8424f483123e37f40c9fb625ef19fbed0b80e19 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Wed, 24 Apr 2024 09:00:39 +0100 Subject: [PATCH 075/222] LogContext: Fix structured metadata labels being added as stream selectors (#86825) * LogContext: Fix structured metadata labels being added as stream selectors * use row index --- .../loki/LogContextProvider.test.ts | 37 +++++++++++-------- .../datasource/loki/LogContextProvider.ts | 14 +++---- .../loki/components/LokiContextUi.test.tsx | 2 +- .../loki/components/LokiContextUi.tsx | 2 +- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/public/app/plugins/datasource/loki/LogContextProvider.test.ts b/public/app/plugins/datasource/loki/LogContextProvider.test.ts index 0eb962a89df..7d6216a8b10 100644 --- a/public/app/plugins/datasource/loki/LogContextProvider.test.ts +++ b/public/app/plugins/datasource/loki/LogContextProvider.test.ts @@ -37,6 +37,11 @@ const defaultLogRow = { type: FieldType.time, values: [0], }, + { + name: 'labelTypes', + type: FieldType.other, + values: [{ bar: 'I', foo: 'S', xyz: 'I' }], + }, ], }), labels: { bar: 'baz', foo: 'uniqueParsedLabel', xyz: 'abc' }, @@ -75,7 +80,7 @@ describe('LogContextProvider', () => { ); expect(logContextProvider.getInitContextFilters).toBeCalled(); expect(logContextProvider.getInitContextFilters).toHaveBeenCalledWith( - { bar: 'baz', foo: 'uniqueParsedLabel', xyz: 'abc' }, + expect.objectContaining({ labels: { bar: 'baz', foo: 'uniqueParsedLabel', xyz: 'abc' } }), { expr: '{bar="baz"}', refId: 'A' }, { from: dateTime(defaultLogRow.timeEpochMs), @@ -399,7 +404,7 @@ describe('LogContextProvider', () => { }; it('should correctly create contextFilters', async () => { - const result = await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithoutParser); + const result = await logContextProvider.getInitContextFilters(defaultLogRow, queryWithoutParser); expect(result.contextFilters).toEqual([ { enabled: true, nonIndexed: false, label: 'bar', value: 'baz' }, { enabled: false, nonIndexed: true, label: 'foo', value: 'uniqueParsedLabel' }, @@ -409,28 +414,29 @@ describe('LogContextProvider', () => { }); it('should return empty contextFilters if no query', async () => { - const filters = (await logContextProvider.getInitContextFilters(defaultLogRow.labels, undefined)) - .contextFilters; + const filters = (await logContextProvider.getInitContextFilters(defaultLogRow, undefined)).contextFilters; expect(filters).toEqual([]); }); it('should return empty contextFilters if no labels', async () => { - const filters = (await logContextProvider.getInitContextFilters({}, queryWithoutParser)).contextFilters; + const filters = ( + await logContextProvider.getInitContextFilters({ labels: [] } as unknown as LogRowModel, queryWithoutParser) + ).contextFilters; expect(filters).toEqual([]); }); it('should call fetchSeriesLabels if parser', async () => { - await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithParser); + await logContextProvider.getInitContextFilters(defaultLogRow, queryWithParser); expect(defaultLanguageProviderMock.fetchSeriesLabels).toBeCalled(); }); it('should call fetchSeriesLabels with given time range', async () => { - await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithParser, timeRange); + await logContextProvider.getInitContextFilters(defaultLogRow, queryWithParser, timeRange); expect(defaultLanguageProviderMock.fetchSeriesLabels).toBeCalledWith(`{bar="baz"}`, { timeRange }); }); it('should call `languageProvider.start` if no parser with given time range', async () => { - await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithoutParser, timeRange); + await logContextProvider.getInitContextFilters(defaultLogRow, queryWithoutParser, timeRange); expect(defaultLanguageProviderMock.start).toBeCalledWith(timeRange); }); }); @@ -442,7 +448,7 @@ describe('LogContextProvider', () => { }; it('should correctly create contextFilters', async () => { - const result = await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithParser); + const result = await logContextProvider.getInitContextFilters(defaultLogRow, queryWithParser); expect(result.contextFilters).toEqual([ { enabled: true, nonIndexed: false, label: 'bar', value: 'baz' }, { enabled: false, nonIndexed: true, label: 'foo', value: 'uniqueParsedLabel' }, @@ -452,13 +458,14 @@ describe('LogContextProvider', () => { }); it('should return empty contextFilters if no query', async () => { - const filters = (await logContextProvider.getInitContextFilters(defaultLogRow.labels, undefined)) - .contextFilters; + const filters = (await logContextProvider.getInitContextFilters(defaultLogRow, undefined)).contextFilters; expect(filters).toEqual([]); }); it('should return empty contextFilters if no labels', async () => { - const filters = (await logContextProvider.getInitContextFilters({}, queryWithParser)).contextFilters; + const filters = ( + await logContextProvider.getInitContextFilters({ labels: [] } as unknown as LogRowModel, queryWithParser) + ).contextFilters; expect(filters).toEqual([]); }); }); @@ -477,7 +484,7 @@ describe('LogContextProvider', () => { selectedExtractedLabels: ['foo'], }) ); - const result = await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithParser); + const result = await logContextProvider.getInitContextFilters(defaultLogRow, queryWithParser); expect(result.contextFilters).toEqual([ { enabled: false, nonIndexed: false, label: 'bar', value: 'baz' }, // disabled real label { enabled: true, nonIndexed: true, label: 'foo', value: 'uniqueParsedLabel' }, // enabled parsed label @@ -494,7 +501,7 @@ describe('LogContextProvider', () => { selectedExtractedLabels: ['foo'], }) ); - const result = await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithParser); + const result = await logContextProvider.getInitContextFilters(defaultLogRow, queryWithParser); expect(result.contextFilters).toEqual([ { enabled: true, nonIndexed: false, label: 'bar', value: 'baz' }, // enabled real label { enabled: false, nonIndexed: true, label: 'foo', value: 'uniqueParsedLabel' }, @@ -511,7 +518,7 @@ describe('LogContextProvider', () => { selectedExtractedLabels: ['foo', 'new'], }) ); - const result = await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithParser); + const result = await logContextProvider.getInitContextFilters(defaultLogRow, queryWithParser); expect(result.contextFilters).toEqual([ { enabled: false, nonIndexed: false, label: 'bar', value: 'baz' }, { enabled: true, nonIndexed: true, label: 'foo', value: 'uniqueParsedLabel' }, diff --git a/public/app/plugins/datasource/loki/LogContextProvider.ts b/public/app/plugins/datasource/loki/LogContextProvider.ts index e7e45999ddd..ada99fad88c 100644 --- a/public/app/plugins/datasource/loki/LogContextProvider.ts +++ b/public/app/plugins/datasource/loki/LogContextProvider.ts @@ -16,11 +16,10 @@ import { dateTime, } from '@grafana/data'; import { LabelParser, LabelFilter, LineFilters, PipelineStage, Logfmt, Json } from '@grafana/lezer-logql'; -import { Labels } from '@grafana/schema'; import { LokiContextUi } from './components/LokiContextUi'; import { LokiDatasource, makeRequest, REF_ID_STARTER_LOG_ROW_CONTEXT } from './datasource'; -import { escapeLabelValueInExactSelector } from './languageUtils'; +import { escapeLabelValueInExactSelector, getLabelTypeFromFrame } from './languageUtils'; import { addLabelToQuery, addParserToQuery } from './modifyQuery'; import { getNodePositionsFromQuery, @@ -61,7 +60,7 @@ export class LogContextProvider { // to use the cached filters, we need to reinitialize them. if (this.cachedContextFilters.length === 0 || !cacheFilters) { const filters = ( - await this.getInitContextFilters(row.labels, origQuery, { + await this.getInitContextFilters(row, origQuery, { from: dateTime(row.timeEpochMs), to: dateTime(row.timeEpochMs), raw: { from: dateTime(row.timeEpochMs), to: dateTime(row.timeEpochMs) }, @@ -312,14 +311,15 @@ export class LogContextProvider { }; getInitContextFilters = async ( - labels: Labels, + row: LogRowModel, query?: LokiQuery, timeRange?: TimeRange ): Promise<{ contextFilters: ContextFilter[]; preservedFiltersApplied: boolean }> => { let preservedFiltersApplied = false; - if (!query || isEmpty(labels)) { + if (!query || isEmpty(row.labels)) { return { contextFilters: [], preservedFiltersApplied }; } + const rowLabels = row.labels; // 1. First we need to get all labels from the log row's label // and correctly set parsed and not parsed labels @@ -338,12 +338,12 @@ export class LogContextProvider { } const contextFilters: ContextFilter[] = []; - Object.entries(labels).forEach(([label, value]) => { + Object.entries(rowLabels).forEach(([label, value]) => { const filter: ContextFilter = { label, value: value, enabled: allLabels.includes(label), - nonIndexed: !allLabels.includes(label), + nonIndexed: getLabelTypeFromFrame(label, row.dataFrame, row.rowIndex) !== LabelType.Indexed, }; contextFilters.push(filter); diff --git a/public/app/plugins/datasource/loki/components/LokiContextUi.test.tsx b/public/app/plugins/datasource/loki/components/LokiContextUi.test.tsx index 0ba89331811..7d9d7220877 100644 --- a/public/app/plugins/datasource/loki/components/LokiContextUi.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiContextUi.test.tsx @@ -122,7 +122,7 @@ describe('LokiContextUi', () => { render(); await waitFor(() => { - expect(props.logContextProvider.getInitContextFilters).toHaveBeenCalledWith(props.row.labels, props.origQuery, { + expect(props.logContextProvider.getInitContextFilters).toHaveBeenCalledWith(props.row, props.origQuery, { from: dateTime(props.row.timeEpochMs), to: dateTime(props.row.timeEpochMs), raw: { from: dateTime(props.row.timeEpochMs), to: dateTime(props.row.timeEpochMs) }, diff --git a/public/app/plugins/datasource/loki/components/LokiContextUi.tsx b/public/app/plugins/datasource/loki/components/LokiContextUi.tsx index abea3b584c2..fe181e08ea8 100644 --- a/public/app/plugins/datasource/loki/components/LokiContextUi.tsx +++ b/public/app/plugins/datasource/loki/components/LokiContextUi.tsx @@ -205,7 +205,7 @@ export function LokiContextUi(props: LokiContextUiProps) { useAsync(async () => { setLoading(true); - const initContextFilters = await logContextProvider.getInitContextFilters(row.labels, origQuery, { + const initContextFilters = await logContextProvider.getInitContextFilters(row, origQuery, { from: dateTime(row.timeEpochMs), to: dateTime(row.timeEpochMs), raw: { from: dateTime(row.timeEpochMs), to: dateTime(row.timeEpochMs) }, From de589b98c7f5e4d8cb5235a090e82aa8a726662c Mon Sep 17 00:00:00 2001 From: Alexa V <239999+axelavargas@users.noreply.github.com> Date: Wed, 24 Apr 2024 10:21:01 +0200 Subject: [PATCH 076/222] Dashboard: Migration [Panel Edit] Missing Query Editor when datasource is not found (#86789) * Return default datasource if datasource is not found * Set query runner datasource state to default, else refreshing will not work --- .../panel-edit/VizPanelManager.test.tsx | 40 ++++++++- .../panel-edit/VizPanelManager.tsx | 18 ++++ .../panel-edit/testfiles/testDashboard.ts | 89 +++++++++++++++++++ 3 files changed, 144 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx index 38390a8d4cc..db2f71c3a6f 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx @@ -1,7 +1,7 @@ import { map, of } from 'rxjs'; import { DataQueryRequest, DataSourceApi, DataSourceInstanceSettings, LoadingState, PanelData } from '@grafana/data'; -import { locationService } from '@grafana/runtime'; +import { config, locationService } from '@grafana/runtime'; import { SceneQueryRunner, VizPanel } from '@grafana/scenes'; import { DataQuery, DataSourceJsonData, DataSourceRef } from '@grafana/schema'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; @@ -75,6 +75,18 @@ const ds3Mock: DataSourceApi = { }, } as DataSourceApi; +const defaultDsMock: DataSourceApi = { + meta: { + id: 'grafana-testdata-datasource', + }, + name: 'grafana-testdata-datasource', + type: 'grafana-testdata-datasource', + uid: 'gdev-testdata', + getRef: () => { + return { type: 'grafana-testdata-datasource', uid: 'gdev-testdata' }; + }, +} as DataSourceApi; + const instance1SettingsMock = { id: 1, uid: 'gdev-testdata', @@ -124,6 +136,7 @@ jest.mock('@grafana/runtime', () => ({ getDataSourceSrv: () => ({ get: async (ref: DataSourceRef) => { // Mocking the build in Grafana data source to avoid annotations data layer errors. + if (ref.uid === '-- Grafana --') { return grafanaDs; } @@ -140,7 +153,8 @@ jest.mock('@grafana/runtime', () => ({ return ds3Mock; } - return null; + // if datasource is not found, return default datasource + return defaultDsMock; }, getInstanceSettings: (ref: DataSourceRef) => { if (ref.uid === 'gdev-testdata') { @@ -151,12 +165,17 @@ jest.mock('@grafana/runtime', () => ({ return instance2SettingsMock; } - return null; + // if datasource is not found, return default instance settings + return instance1SettingsMock; }, }), locationService: { partial: jest.fn(), }, + config: { + ...jest.requireActual('@grafana/runtime').config, + defaultDatasource: 'gdev-testdata', + }, })); describe('VizPanelManager', () => { @@ -350,6 +369,21 @@ describe('VizPanelManager', () => { datasourceUid: 'gdev-testdata', }); }); + + it('should load default datasource if the datasource passed is not found', async () => { + const { vizPanelManager } = setupTest('panel-6'); + vizPanelManager.activate(); + await Promise.resolve(); + + expect(vizPanelManager.queryRunner.state.datasource).toEqual({ + uid: 'abc', + type: 'datasource', + }); + + expect(config.defaultDatasource).toBe('gdev-testdata'); + expect(vizPanelManager.state.datasource).toEqual(defaultDsMock); + expect(vizPanelManager.state.dsSettings).toEqual(instance1SettingsMock); + }); }); describe('data source change', () => { diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx index 6979675ee09..0cfa0f7c5bc 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx @@ -154,6 +154,24 @@ export class VizPanelManager extends SceneObjectBase { ); } } catch (err) { + //set default datasource if we fail to load the datasource + const datasource = await getDataSourceSrv().get(config.defaultDatasource); + const dsSettings = getDataSourceSrv().getInstanceSettings(config.defaultDatasource); + + if (datasource && dsSettings) { + this.setState({ + datasource, + dsSettings, + }); + + this.queryRunner.setState({ + datasource: { + uid: dsSettings.uid, + type: dsSettings.type, + }, + }); + } + console.error(err); } } diff --git a/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.ts b/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.ts index f524e20f99c..77fec6d6910 100644 --- a/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.ts +++ b/public/app/features/dashboard-scene/panel-edit/testfiles/testDashboard.ts @@ -410,6 +410,94 @@ export const panelWithNoDataSource = { title: 'Panel with no data source', type: 'timeseries', }; + +export const panelWithDataSourceNotFound = { + datasource: { + type: 'datasource', + uid: 'abc', + }, + fieldConfig: { + defaults: { + color: { + mode: 'palette-classic', + }, + custom: { + axisBorderShow: false, + axisCenteredZero: false, + axisColorMode: 'text', + axisLabel: '', + axisPlacement: 'auto', + barAlignment: 0, + drawStyle: 'line', + fillOpacity: 0, + gradientMode: 'none', + hideFrom: { + legend: false, + tooltip: false, + viz: false, + }, + insertNulls: false, + lineInterpolation: 'linear', + lineWidth: 1, + pointSize: 5, + scaleDistribution: { + type: 'linear', + }, + showPoints: 'auto', + spanNulls: false, + stacking: { + group: 'A', + mode: 'none', + }, + thresholdsStyle: { + mode: 'off', + }, + }, + mappings: [], + thresholds: { + mode: 'absolute', + steps: [ + { + color: 'green', + value: null, + }, + { + color: 'red', + value: 80, + }, + ], + }, + }, + overrides: [], + }, + gridPos: { + h: 8, + w: 12, + x: 0, + y: 0, + }, + id: 6, + options: { + legend: { + calcs: [], + displayMode: 'list', + placement: 'bottom', + showLegend: true, + }, + tooltip: { + mode: 'single', + sort: 'none', + }, + }, + targets: [ + { + refId: 'A', + }, + ], + title: 'Panel with no data source', + type: 'timeseries', +}; + export const testDashboard = { annotations: { list: [ @@ -439,6 +527,7 @@ export const testDashboard = { panelWithDashboardQuery, panelWithDashboardQueryAndTransformations, panelWithNoDataSource, + panelWithDataSourceNotFound, ], refresh: '', schemaVersion: 39, From f6e472f8797cde4dfdc3ab4f1fc3fc5969b7c8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Jamr=C3=B3z?= Date: Wed, 24 Apr 2024 10:32:11 +0200 Subject: [PATCH 077/222] Explore: Show a drawer with tabs for the library and query history (#86279) * Create basic feature toggle * Rename context to reflect it contains query history and query library * Update icons and variants * Rename hooks * Update tests * Fix mock * Add tracking * Turn button into a toggle * Make dropdown active as well This is required to have better UI and an indication of selected state in split view * Update Query Library icon This is to make it consistent with the toolbar button * Hide query history button when query library is available This is to avoid confusing UX with 2 button triggering the drawer but with slightly different behavior * Make the drawer bigger for query library To avoid confusion for current users and test it internally a bit more it's behind a feature toggle. Bigger drawer may obstruct the view and add more friction in the UX. * Fix tests The test was failing because queryLibraryAvailable was set to true for tests. This change makes it more explicit what use case is being tested * Remove active state underline from the dropdown * Allow closing Query Library drawer from the toolbar * Simplify dropdown design --- .../feature-toggles/index.md | 1 + .../src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/registry.go | 8 +++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++ pkg/services/featuremgmt/toggles_gen.json | 17 ++++- public/app/features/explore/Explore.test.tsx | 4 +- public/app/features/explore/Explore.tsx | 11 +--- public/app/features/explore/ExploreDrawer.tsx | 7 +- public/app/features/explore/ExplorePage.tsx | 23 ++++--- .../app/features/explore/ExploreToolbar.tsx | 2 + .../QueriesDrawer/QueriesDrawerContext.tsx | 63 ++++++++++++++++++ .../QueriesDrawer/QueriesDrawerDropdown.tsx | 66 +++++++++++++++++++ .../features/explore/QueriesDrawer/mocks.tsx | 30 +++++++++ .../features/explore/QueriesDrawer/utils.ts | 6 ++ .../app/features/explore/QueryRows.test.tsx | 1 - .../explore/RichHistory/RichHistory.test.tsx | 4 +- .../explore/RichHistory/RichHistory.tsx | 23 ++++--- .../RichHistory/RichHistoryContainer.test.tsx | 2 - .../RichHistory/RichHistoryContainer.tsx | 28 +++++--- .../explore/SecondaryActions.test.tsx | 51 ++++++++------ .../app/features/explore/SecondaryActions.tsx | 15 +++-- public/app/features/explore/state/main.ts | 10 --- .../app/features/explore/state/selectors.ts | 4 +- public/app/types/explore.ts | 5 -- public/locales/en-US/grafana.json | 1 + public/locales/pseudo-LOCALE/grafana.json | 1 + 27 files changed, 300 insertions(+), 89 deletions(-) create mode 100644 public/app/features/explore/QueriesDrawer/QueriesDrawerContext.tsx create mode 100644 public/app/features/explore/QueriesDrawer/QueriesDrawerDropdown.tsx create mode 100644 public/app/features/explore/QueriesDrawer/mocks.tsx create mode 100644 public/app/features/explore/QueriesDrawer/utils.ts diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 4a9c62d7e94..9c71a50e4d3 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -176,6 +176,7 @@ Experimental features might be changed or removed without prior notice. | `expressionParser` | Enable new expression parser | | `accessActionSets` | Introduces action sets for resource permissions | | `disableNumericMetricsSortingInExpressions` | In server-side expressions, disable the sorting of numeric-kind metrics by their metric name or labels. | +| `queryLibrary` | Enables Query Library feature in Explore | ## Development feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index ecdc7a62038..7fbc72bfdf3 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -180,4 +180,5 @@ export interface FeatureToggles { accessActionSets?: boolean; disableNumericMetricsSortingInExpressions?: boolean; grafanaManagedRecordingRules?: boolean; + queryLibrary?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 81948562368..8f9af951279 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1211,6 +1211,14 @@ var ( HideFromDocs: true, HideFromAdminPage: true, }, + { + Name: "queryLibrary", + Description: "Enables Query Library feature in Explore", + Stage: FeatureStageExperimental, + Owner: grafanaExploreSquad, + FrontendOnly: false, + AllowSelfServe: false, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 8527a1865ac..fe1848088b8 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -161,3 +161,4 @@ cloudWatchNewLabelParsing,GA,@grafana/aws-datasources,false,false,false accessActionSets,experimental,@grafana/identity-access-team,false,false,false disableNumericMetricsSortingInExpressions,experimental,@grafana/observability-metrics,false,true,false grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,false +queryLibrary,experimental,@grafana/explore-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index d98b2823705..658e9d58dbd 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -654,4 +654,8 @@ const ( // FlagGrafanaManagedRecordingRules // Enables Grafana-managed recording rules. FlagGrafanaManagedRecordingRules = "grafanaManagedRecordingRules" + + // FlagQueryLibrary + // Enables Query Library feature in Explore + FlagQueryLibrary = "queryLibrary" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 9ee35efc3e3..d72a6888c07 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2088,6 +2088,21 @@ "hideFromAdminPage": true, "hideFromDocs": true } + }, + { + "metadata": { + "name": "queryLibrary", + "resourceVersion": "1713260947272", + "creationTimestamp": "2024-04-16T07:18:28Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-04-16 09:49:07.272595 +0000 UTC" + } + }, + "spec": { + "description": "Enables Query Library feature in Explore", + "stage": "experimental", + "codeowner": "@grafana/explore-squad" + } } ] -} \ No newline at end of file +} diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx index d004eb99473..70de7d816ad 100644 --- a/public/app/features/explore/Explore.test.tsx +++ b/public/app/features/explore/Explore.test.tsx @@ -10,7 +10,7 @@ import { configureStore } from 'app/store/configureStore'; import { ContentOutlineContextProvider } from './ContentOutline/ContentOutlineContext'; import { Explore, Props } from './Explore'; -import { changeShowQueryHistory, initialExploreState } from './state/main'; +import { initialExploreState } from './state/main'; import { scanStopAction } from './state/query'; import { createEmptyQueryResponse, makeExplorePaneState } from './state/utils'; @@ -100,8 +100,6 @@ const dummyProps: Props = { setSupplementaryQueryEnabled: jest.fn(), correlationEditorDetails: undefined, correlationEditorHelperData: undefined, - showQueryHistory: false, - changeShowQueryHistory: changeShowQueryHistory, }; jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => { diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 6726e92a4e3..91313cd2e3f 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -56,7 +56,7 @@ import { SecondaryActions } from './SecondaryActions'; import TableContainer from './Table/TableContainer'; import { TraceViewContainer } from './TraceView/TraceViewContainer'; import { changeSize } from './state/explorePane'; -import { changeShowQueryHistory, splitOpen } from './state/main'; +import { splitOpen } from './state/main'; import { addQueryRow, modifyQueries, @@ -304,10 +304,6 @@ export class Explore extends React.PureComponent { updateTimeRange({ exploreId, absoluteRange }); }; - toggleShowQueryHistory = () => { - this.props.changeShowQueryHistory(!this.props.showQueryHistory); - }; - onSplitOpen = (panelType: string) => { return async (options?: SplitOpenOptions) => { this.props.splitOpen(options); @@ -535,7 +531,6 @@ export class Explore extends React.PureComponent { showLogsSample, correlationEditorDetails, correlationEditorHelperData, - showQueryHistory, showQueryInspector, setShowQueryInspector, } = this.props; @@ -603,10 +598,8 @@ export class Explore extends React.PureComponent { //TODO:unification addQueryRowButtonHidden={false} richHistoryRowButtonHidden={richHistoryRowButtonHidden} - richHistoryButtonActive={showQueryHistory} queryInspectorButtonActive={showQueryInspector} onClickAddQueryRowButton={this.onClickAddQueryRowButton} - onClickRichHistoryButton={this.toggleShowQueryHistory} onClickQueryInspectorButton={() => setShowQueryInspector(!showQueryInspector)} /> @@ -721,7 +714,6 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) { showLogsSample, correlationEditorHelperData, correlationEditorDetails: explore.correlationEditorDetails, - showQueryHistory: explore.showQueryHistory, }; } @@ -735,7 +727,6 @@ const mapDispatchToProps = { addQueryRow, splitOpen, setSupplementaryQueryEnabled, - changeShowQueryHistory, }; const connector = connect(mapStateToProps, mapDispatchToProps); diff --git a/public/app/features/explore/ExploreDrawer.tsx b/public/app/features/explore/ExploreDrawer.tsx index 03c595e88a4..af4c41922e4 100644 --- a/public/app/features/explore/ExploreDrawer.tsx +++ b/public/app/features/explore/ExploreDrawer.tsx @@ -10,18 +10,21 @@ import { getDragStyles, useStyles2, useTheme2 } from '@grafana/ui'; export interface Props { children: React.ReactNode; onResize?: ResizeCallback; + initialHeight?: string; } export function ExploreDrawer(props: Props) { - const { children, onResize } = props; + const { children, onResize, initialHeight } = props; const theme = useTheme2(); const styles = useStyles2(getStyles); const dragStyles = getDragStyles(theme); + const height = initialHeight || `${theme.components.horizontalDrawer.defaultHeight}px`; + return ( ) { + return ( + + + + ); +} + +function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryParams>) { const styles = useStyles2(getStyles); const theme = useTheme2(); useTimeSrvFix(); @@ -40,13 +48,12 @@ export default function ExplorePage(props: GrafanaRouteComponentProps<{}, Explor useExplorePageTitle(props.queryParams); const { chrome } = useGrafana(); const navModel = useNavModel('explore'); - const dispatch = useDispatch(); const { updateSplitSize, widthCalc } = useSplitSizeUpdater(MIN_PANE_WIDTH); const panes = useSelector(selectPanesEntries); const hasSplit = useSelector(isSplit); const correlationDetails = useSelector(selectCorrelationDetails); - const showQueryHistory = useSelector(selectShowQueryHistory); + const { drawerOpened, setDrawerOpened, queryLibraryAvailable } = useQueriesDrawerContext(); const showCorrelationEditorBar = config.featureToggles.correlations && (correlationDetails?.editorMode || false); useEffect(() => { @@ -89,11 +96,11 @@ export default function ExplorePage(props: GrafanaRouteComponentProps<{}, Explor ); })} - {showQueryHistory && ( - + {drawerOpened && ( + { - dispatch(changeShowQueryHistory(false)); + setDrawerOpened(false); }} /> diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index f88010d9d6b..b762b57b6f5 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -26,6 +26,7 @@ import { getFiscalYearStartMonth, getTimeZone } from '../profile/state/selectors import { ExploreTimeControls } from './ExploreTimeControls'; import { LiveTailButton } from './LiveTailButton'; +import { QueriesDrawerDropdown } from './QueriesDrawer/QueriesDrawerDropdown'; import { ShortLinkButtonMenu } from './ShortLinkButtonMenu'; import { ToolbarExtensionPoint } from './extensions/ToolbarExtensionPoint'; import { changeDatasource } from './state/datasource'; @@ -238,6 +239,7 @@ export function ExploreToolbar({ exploreId, onChangeTime, onContentOutlineToogle forceShowLeftItems > {[ + , !splitted ? ( void; + queryLibraryAvailable: boolean; + drawerOpened: boolean; + setDrawerOpened: (value: boolean) => void; +}; + +export const QueriesDrawerContext = createContext({ + selectedTab: undefined, + setSelectedTab: () => {}, + queryLibraryAvailable: false, + drawerOpened: false, + setDrawerOpened: () => {}, +}); + +export function useQueriesDrawerContext() { + return useContext(QueriesDrawerContext); +} + +export function QueriesDrawerContextProvider({ children }: PropsWithChildren) { + const queryLibraryAvailable = config.featureToggles.queryLibrary === true; + const [selectedTab, setSelectedTab] = useState( + queryLibraryAvailable ? Tabs.QueryLibrary : undefined + ); + const [drawerOpened, setDrawerOpened] = useState(false); + + const settings = useSelector(selectRichHistorySettings); + + useEffect(() => { + if (settings && !queryLibraryAvailable) { + setSelectedTab(settings.starredTabAsFirstTab ? Tabs.Starred : Tabs.RichHistory); + } + }, [settings, setSelectedTab, queryLibraryAvailable]); + + return ( + + {children} + + ); +} diff --git a/public/app/features/explore/QueriesDrawer/QueriesDrawerDropdown.tsx b/public/app/features/explore/QueriesDrawer/QueriesDrawerDropdown.tsx new file mode 100644 index 00000000000..238cfc06da1 --- /dev/null +++ b/public/app/features/explore/QueriesDrawer/QueriesDrawerDropdown.tsx @@ -0,0 +1,66 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { Button, ButtonGroup, Dropdown, Menu, ToolbarButton } from '@grafana/ui'; +import { useStyles2 } from '@grafana/ui/'; + +import { Tabs, useQueriesDrawerContext } from './QueriesDrawerContext'; +import { i18n } from './utils'; + +type Props = { + variant: 'compact' | 'full'; +}; + +export function QueriesDrawerDropdown({ variant }: Props) { + const { selectedTab, setSelectedTab, queryLibraryAvailable, drawerOpened, setDrawerOpened } = + useQueriesDrawerContext(); + + const styles = useStyles2(getStyles); + + if (!queryLibraryAvailable) { + return undefined; + } + + function toggle(tab: Tabs) { + setSelectedTab(tab); + setDrawerOpened(false); + setDrawerOpened(true); + } + + const menu = ( + + toggle(Tabs.QueryLibrary)} /> + toggle(Tabs.RichHistory)} /> + + ); + + return ( + + setDrawerOpened(!drawerOpened)} + > + {variant === 'full' ? selectedTab : undefined} + + {drawerOpened ? ( + + ) : ( + + + + )} + + ); +} + +const getStyles = () => ({ + toggle: css({ width: '36px' }), + // tweaking icon position so it's nicely aligned when dropdown turns into a close button + close: css({ width: '36px', '> svg': { position: 'relative', left: 2 } }), +}); diff --git a/public/app/features/explore/QueriesDrawer/mocks.tsx b/public/app/features/explore/QueriesDrawer/mocks.tsx new file mode 100644 index 00000000000..ed9d5ceb590 --- /dev/null +++ b/public/app/features/explore/QueriesDrawer/mocks.tsx @@ -0,0 +1,30 @@ +import React, { PropsWithChildren, useState } from 'react'; + +import { QueriesDrawerContext, Tabs } from './QueriesDrawerContext'; + +type Props = { + setDrawerOpened?: (value: boolean) => {}; + queryLibraryAvailable?: boolean; +} & PropsWithChildren; + +export function QueriesDrawerContextProviderMock(props: Props) { + const [selectedTab, setSelectedTab] = useState(Tabs.QueryLibrary); + const [drawerOpened, setDrawerOpened] = useState(false); + + return ( + { + props.setDrawerOpened?.(value); + setDrawerOpened(value); + }, + }} + > + {props.children} + + ); +} diff --git a/public/app/features/explore/QueriesDrawer/utils.ts b/public/app/features/explore/QueriesDrawer/utils.ts new file mode 100644 index 00000000000..fa77d88fe13 --- /dev/null +++ b/public/app/features/explore/QueriesDrawer/utils.ts @@ -0,0 +1,6 @@ +import { t } from 'app//core/internationalization'; + +export const i18n = { + queryLibrary: t('explore.rich-history.query-library', 'Query library'), + queryHistory: t('explore.rich-history.query-history', 'Query history'), +}; diff --git a/public/app/features/explore/QueryRows.test.tsx b/public/app/features/explore/QueryRows.test.tsx index 07e6d5881d2..a65ebe6c18a 100644 --- a/public/app/features/explore/QueryRows.test.tsx +++ b/public/app/features/explore/QueryRows.test.tsx @@ -52,7 +52,6 @@ function setup(queries: DataQuery[]) { const leftState = makeExplorePaneState(); const initialState: ExploreState = { richHistory: [], - showQueryHistory: false, panes: { left: { ...leftState, diff --git a/public/app/features/explore/RichHistory/RichHistory.test.tsx b/public/app/features/explore/RichHistory/RichHistory.test.tsx index 35987fa1773..ff53cbd57f4 100644 --- a/public/app/features/explore/RichHistory/RichHistory.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistory.test.tsx @@ -4,7 +4,9 @@ import { TestProvider } from 'test/helpers/TestProvider'; import { SortOrder } from 'app/core/utils/richHistory'; -import { RichHistory, RichHistoryProps, Tabs } from './RichHistory'; +import { Tabs } from '../QueriesDrawer/QueriesDrawerContext'; + +import { RichHistory, RichHistoryProps } from './RichHistory'; jest.mock('../state/selectors', () => ({ selectExploreDSMaps: jest.fn().mockReturnValue({ dsToExplore: [] }) })); diff --git a/public/app/features/explore/RichHistory/RichHistory.tsx b/public/app/features/explore/RichHistory/RichHistory.tsx index fc0158b6220..8382edc323b 100644 --- a/public/app/features/explore/RichHistory/RichHistory.tsx +++ b/public/app/features/explore/RichHistory/RichHistory.tsx @@ -3,23 +3,19 @@ import React, { useState, useEffect } from 'react'; import { SelectableValue } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { TabbedContainer, TabConfig } from '@grafana/ui'; +import { EmptyState, TabbedContainer, TabConfig } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { SortOrder, RichHistorySearchFilters, RichHistorySettings } from 'app/core/utils/richHistory'; import { RichHistoryQuery } from 'app/types/explore'; import { supportedFeatures } from '../../../core/history/richHistoryStorageProvider'; +import { Tabs, useQueriesDrawerContext } from '../QueriesDrawer/QueriesDrawerContext'; +import { i18n } from '../QueriesDrawer/utils'; import { RichHistoryQueriesTab } from './RichHistoryQueriesTab'; import { RichHistorySettingsTab } from './RichHistorySettingsTab'; import { RichHistoryStarredTab } from './RichHistoryStarredTab'; -export enum Tabs { - RichHistory = 'Query history', - Starred = 'Starred', - Settings = 'Settings', -} - export const getSortOrderOptions = () => [ { label: t('explore.rich-history.newest-first', 'Newest first'), value: SortOrder.Descending }, @@ -49,6 +45,8 @@ export function RichHistory(props: RichHistoryProps) { const [loading, setLoading] = useState(false); + const { queryLibraryAvailable } = useQueriesDrawerContext(); + const updateSettings = (settingsToUpdate: Partial) => { props.updateHistorySettings({ ...props.richHistorySettings, ...settingsToUpdate }); }; @@ -84,8 +82,15 @@ export function RichHistory(props: RichHistoryProps) { setLoading(false); }, [richHistory]); + const QueryLibraryTab: TabConfig = { + label: i18n.queryLibrary, + value: Tabs.QueryLibrary, + content: , + icon: 'book', + }; + const QueriesTab: TabConfig = { - label: t('explore.rich-history.query-history', 'Query history'), + label: i18n.queryHistory, value: Tabs.RichHistory, content: ( ({ @@ -27,7 +26,6 @@ jest.mock('../state/selectors', () => ({ selectExploreDSMaps: jest.fn().mockRetu const setup = (propOverrides?: Partial) => { const props: Props = { richHistory: [], - firstTab: Tabs.RichHistory, deleteRichHistory: jest.fn(), initRichHistory: jest.fn(), loadRichHistory: jest.fn(), diff --git a/public/app/features/explore/RichHistory/RichHistoryContainer.tsx b/public/app/features/explore/RichHistory/RichHistoryContainer.tsx index acaf618b636..ed14ac0b352 100644 --- a/public/app/features/explore/RichHistory/RichHistoryContainer.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryContainer.tsx @@ -1,5 +1,5 @@ // Libraries -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { config, reportInteraction } from '@grafana/runtime'; @@ -9,6 +9,7 @@ import { Trans } from 'app/core/internationalization'; import { StoreState } from 'app/types'; // Components, enums +import { useQueriesDrawerContext } from '../QueriesDrawer/QueriesDrawerContext'; import { deleteRichHistory, initRichHistory, @@ -19,7 +20,7 @@ import { updateHistorySearchFilters, } from '../state/history'; -import { RichHistory, Tabs } from './RichHistory'; +import { RichHistory } from './RichHistory'; //Actions @@ -28,11 +29,9 @@ function mapStateToProps(state: StoreState) { const richHistorySearchFilters = explore.richHistorySearchFilters; const { richHistorySettings, richHistory, richHistoryTotal } = explore; - const firstTab = richHistorySettings?.starredTabAsFirstTab ? Tabs.Starred : Tabs.RichHistory; return { richHistory, richHistoryTotal, - firstTab, richHistorySettings, richHistorySearchFilters, }; @@ -61,7 +60,6 @@ export function RichHistoryContainer(props: Props) { const { richHistory, richHistoryTotal, - firstTab, deleteRichHistory, initRichHistory, loadRichHistory, @@ -76,12 +74,22 @@ export function RichHistoryContainer(props: Props) { useEffect(() => { initRichHistory(); - reportInteraction('grafana_explore_query_history_opened', { - queryHistoryEnabled: config.queryHistoryEnabled, - }); }, [initRichHistory]); - if (!richHistorySettings) { + const { selectedTab } = useQueriesDrawerContext(); + const [tracked, setTracked] = useState(false); + + useEffect(() => { + if (!tracked) { + setTracked(true); + reportInteraction('grafana_explore_query_history_opened', { + queryHistoryEnabled: config.queryHistoryEnabled, + selectedTab, + }); + } + }, [tracked, selectedTab]); + + if (!richHistorySettings || !selectedTab) { return ( Loading... @@ -93,7 +101,7 @@ export function RichHistoryContainer(props: Props) { { it('should render component with three buttons', () => { - render( - - ); + render(); expect(screen.getByRole('button', { name: /Add query/i })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /Query history/i })).toBeInTheDocument(); @@ -22,13 +17,14 @@ describe('SecondaryActions', () => { it('should not render hidden elements', () => { render( - + + + ); expect(screen.queryByRole('button', { name: /Add query/i })).not.toBeInTheDocument(); @@ -36,12 +32,25 @@ describe('SecondaryActions', () => { expect(screen.getByRole('button', { name: /Query inspector/i })).toBeInTheDocument(); }); + it('should not render query history button when query library is available', () => { + render( + + + + ); + + expect(screen.queryByRole('button', { name: /Query history/i })).not.toBeInTheDocument(); + }); + it('should disable add row button if addQueryRowButtonDisabled=true', () => { render( ); @@ -59,11 +68,12 @@ describe('SecondaryActions', () => { const onClickQueryInspector = jest.fn(); render( - + + + ); await user.click(screen.getByRole('button', { name: /Add query/i })); @@ -71,6 +81,7 @@ describe('SecondaryActions', () => { await user.click(screen.getByRole('button', { name: /Query history/i })); expect(onClickHistory).toBeCalledTimes(1); + expect(onClickHistory).toBeCalledWith(true); await user.click(screen.getByRole('button', { name: /Query inspector/i })); expect(onClickQueryInspector).toBeCalledTimes(1); diff --git a/public/app/features/explore/SecondaryActions.tsx b/public/app/features/explore/SecondaryActions.tsx index d5c29d43e2e..cdba3e51854 100644 --- a/public/app/features/explore/SecondaryActions.tsx +++ b/public/app/features/explore/SecondaryActions.tsx @@ -6,15 +6,15 @@ import { Components } from '@grafana/e2e-selectors'; import { ToolbarButton, useTheme2 } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; +import { useQueriesDrawerContext } from './QueriesDrawer/QueriesDrawerContext'; + type Props = { addQueryRowButtonDisabled?: boolean; addQueryRowButtonHidden?: boolean; richHistoryRowButtonHidden?: boolean; - richHistoryButtonActive?: boolean; queryInspectorButtonActive?: boolean; onClickAddQueryRowButton: () => void; - onClickRichHistoryButton: () => void; onClickQueryInspectorButton: () => void; }; @@ -32,6 +32,11 @@ const getStyles = (theme: GrafanaTheme2) => { export function SecondaryActions(props: Props) { const theme = useTheme2(); const styles = getStyles(theme); + const { drawerOpened, setDrawerOpened, queryLibraryAvailable } = useQueriesDrawerContext(); + + // When queryLibraryAvailable=true we show the button in the toolbar (see QueriesDrawerDropdown) + const showHistoryButton = !props.richHistoryRowButtonHidden && !queryLibraryAvailable; + return (
{!props.addQueryRowButtonHidden && ( @@ -45,11 +50,11 @@ export function SecondaryActions(props: Props) { Add query )} - {!props.richHistoryRowButtonHidden && ( + {showHistoryButton && ( setDrawerOpened(!drawerOpened)} data-testid={Components.QueryTab.queryHistoryButton} icon="history" > diff --git a/public/app/features/explore/state/main.ts b/public/app/features/explore/state/main.ts index 07c234fa634..2f453f17c25 100644 --- a/public/app/features/explore/state/main.ts +++ b/public/app/features/explore/state/main.ts @@ -124,8 +124,6 @@ export const changeCorrelationEditorDetails = createAction('explore/changeShowQueryHistory'); - export interface NavigateToExploreDependencies { timeRange: TimeRange; getExploreUrl: (args: GetExploreUrlArguments) => Promise; @@ -169,7 +167,6 @@ export const initialExploreState: ExploreState = { largerExploreId: undefined, maxedExploreId: undefined, evenSplitPanes: true, - showQueryHistory: false, richHistory: [], }; @@ -323,13 +320,6 @@ export const exploreReducer = (state = initialExploreState, action: AnyAction): }; } - if (changeShowQueryHistory.match(action)) { - return { - ...state, - showQueryHistory: action.payload, - }; - } - const exploreId: string | undefined = action.payload?.exploreId; if (typeof exploreId === 'string') { return { diff --git a/public/app/features/explore/state/selectors.ts b/public/app/features/explore/state/selectors.ts index a475e8b5b04..9f5ea7f86df 100644 --- a/public/app/features/explore/state/selectors.ts +++ b/public/app/features/explore/state/selectors.ts @@ -7,6 +7,8 @@ import { ExploreItemState, StoreState } from 'app/types'; export const selectPanes = (state: Pick) => state.explore.panes; export const selectExploreRoot = (state: Pick) => state.explore; +export const selectRichHistorySettings = (state: Pick) => state.explore.richHistorySettings; + export const selectPanesEntries = createSelector< [(state: Pick) => Record], Array<[string, ExploreItemState]> @@ -26,8 +28,6 @@ export const getExploreItemSelector = (exploreId: string) => createSelector(sele export const selectCorrelationDetails = createSelector(selectExploreRoot, (state) => state.correlationEditorDetails); -export const selectShowQueryHistory = createSelector(selectExploreRoot, (state) => state.showQueryHistory); - export const selectExploreDSMaps = createSelector(selectPanesEntries, (panes) => { const exploreDSMap = panes .map(([exploreId, pane]) => { diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 3f91e26c6d4..865d656e3dc 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -63,11 +63,6 @@ export interface ExploreState { panes: Record; - /** - * Is the drawer for query history showing - */ - showQueryHistory: boolean; - /** * History of all queries */ diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index bc911111504..b14cc1fe96e 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -472,6 +472,7 @@ "newest-first": "Newest first", "oldest-first": "Oldest first", "query-history": "Query history", + "query-library": "Query library", "settings": "Settings", "starred": "Starred" }, diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 64daa25d135..65abad5652f 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -472,6 +472,7 @@ "newest-first": "Ńęŵęşŧ ƒįřşŧ", "oldest-first": "Øľđęşŧ ƒįřşŧ", "query-history": "Qūęřy ĥįşŧőřy", + "query-library": "Qūęřy ľįþřäřy", "settings": "Ŝęŧŧįʼnģş", "starred": "Ŝŧäřřęđ" }, From 522a98c1266b49345413bf3a5c13c0b86ed08dc9 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Wed, 24 Apr 2024 10:38:40 +0200 Subject: [PATCH 078/222] Chore: Make Cfg field private in SQLStore (#85593) * make cfg private in sqlstore * fix db init in tests * fix case * fix folder test init * fix imports * make another Cfg private * remove another Cfg * remove unused variable * use store cfg, it has side-effects * fix mutated cfg in tests --- pkg/api/dashboard_test.go | 4 +- pkg/api/folder_bench_test.go | 3 +- pkg/api/org_users_test.go | 4 +- pkg/api/user_test.go | 14 ++-- .../commands/conflict_user_command.go | 2 +- .../commands/conflict_user_command_test.go | 24 +++--- pkg/infra/db/db.go | 11 ++- pkg/infra/remotecache/remotecache_test.go | 7 +- .../statscollector/concurrent_users_test.go | 4 +- pkg/server/module_server_test.go | 3 +- .../accesscontrol/database/database_test.go | 2 +- .../resourcepermissions/store_test.go | 4 +- .../accesscontrol/accesscontrol_test.go | 3 +- .../annotationsimpl/annotations_test.go | 9 +- .../loki/historian_store_test.go | 3 +- .../database/database_folder_test.go | 9 +- .../database/database_provisioning_test.go | 4 +- .../dashboards/database/database_test.go | 12 +-- .../folderimpl/dashboard_folder_store_test.go | 4 +- pkg/services/folder/folderimpl/folder_test.go | 82 +++++++++---------- .../folder/folderimpl/sqlstore_test.go | 48 +++++------ .../libraryelements/libraryelements_test.go | 26 +++--- .../librarypanels/librarypanels_test.go | 6 +- pkg/services/org/orgimpl/org.go | 1 - pkg/services/org/orgimpl/store.go | 2 - pkg/services/org/orgimpl/store_test.go | 54 ++++++------ .../plugins_integration_test.go | 3 +- .../publicdashboards/api/query_test.go | 8 +- .../database/database_test.go | 34 ++++---- .../publicdashboards/service/common_test.go | 9 +- .../publicdashboards/service/query_test.go | 4 +- pkg/services/query/query_test.go | 4 +- .../queryhistory/queryhistory_test.go | 6 +- pkg/services/quota/quotaimpl/quota_test.go | 48 +++++------ .../serviceaccounts/database/store_test.go | 3 +- pkg/services/sqlstore/bulk_test.go | 2 +- .../sqlstore/permissions/dashboard_test.go | 6 +- .../permissions/dashboards_bench_test.go | 6 +- pkg/services/sqlstore/session_test.go | 4 +- pkg/services/sqlstore/sqlstore.go | 38 ++++----- pkg/services/sqlstore/sqlstore_test.go | 12 +-- pkg/services/sqlstore/transactions_test.go | 4 +- pkg/services/sqlstore/user.go | 20 ++--- pkg/services/sqlstore/user_test.go | 8 +- pkg/services/stats/statsimpl/stats_test.go | 11 ++- .../sqlstash/sql_storage_server_test.go | 7 +- pkg/services/team/teamimpl/store_test.go | 26 +++--- .../temp_user/tempuserimpl/store_test.go | 12 +-- pkg/services/user/userimpl/store_test.go | 43 +++++----- pkg/tsdb/legacydata/service/service_test.go | 6 +- 50 files changed, 325 insertions(+), 344 deletions(-) diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index be403723bb3..19f4e58c43f 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -814,9 +814,9 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr features := featuremgmt.WithFeatures() var err error if dashboardStore == nil { - sql := db.InitTestDB(t) + sql, cfg := db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) - dashboardStore, err = database.ProvideDashboardStore(sql, sql.Cfg, features, tagimpl.ProvideService(sql), quotaService) + dashboardStore, err = database.ProvideDashboardStore(sql, cfg, features, tagimpl.ProvideService(sql), quotaService) require.NoError(t, err) } diff --git a/pkg/api/folder_bench_test.go b/pkg/api/folder_bench_test.go index b53c9098d01..c7fe58525bd 100644 --- a/pkg/api/folder_bench_test.go +++ b/pkg/api/folder_bench_test.go @@ -201,13 +201,12 @@ func BenchmarkFolderListAndSearch(b *testing.B) { func setupDB(b testing.TB) benchScenario { b.Helper() - db := sqlstore.InitTestDB(b) + db, cfg := sqlstore.InitTestDB(b) IDs := map[int64]struct{}{} opts := sqlstore.NativeSettingsForDialect(db.GetDialect()) quotaService := quotatest.New(false, nil) - cfg := setting.NewCfg() teamSvc, err := teamimpl.ProvideService(db, cfg) require.NoError(b, err) diff --git a/pkg/api/org_users_test.go b/pkg/api/org_users_test.go index 34a52f549b0..94dd640736b 100644 --- a/pkg/api/org_users_test.go +++ b/pkg/api/org_users_test.go @@ -27,6 +27,7 @@ import ( "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/quota/quotaimpl" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user/userimpl" @@ -61,8 +62,7 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) { hs := setupSimpleHTTPServer(featuremgmt.WithFeatures()) settings := hs.Cfg - sqlStore := db.InitTestDB(t) - sqlStore.Cfg = settings + sqlStore := db.InitTestDB(t, sqlstore.InitTestDBOpt{Cfg: settings}) hs.SQLStore = sqlStore orgService := orgtest.NewOrgServiceFake() orgService.ExpectedSearchOrgUsersResult = &org.SearchOrgUsersQueryResult{} diff --git a/pkg/api/user_test.go b/pkg/api/user_test.go index 55bd2dc61d3..5bd8bbd2b68 100644 --- a/pkg/api/user_test.go +++ b/pkg/api/user_test.go @@ -39,6 +39,7 @@ import ( "github.com/grafana/grafana/pkg/services/secrets/database" "github.com/grafana/grafana/pkg/services/secrets/fakes" secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" tempuser "github.com/grafana/grafana/pkg/services/temp_user" "github.com/grafana/grafana/pkg/services/temp_user/tempuserimpl" @@ -53,8 +54,7 @@ const newEmail = "newemail@localhost" func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { settings := setting.NewCfg() - sqlStore := db.InitTestDB(t) - sqlStore.Cfg = settings + sqlStore := db.InitTestDB(t, sqlstore.InitTestDBOpt{Cfg: settings}) hs := &HTTPServer{ Cfg: settings, SQLStore: sqlStore, @@ -78,7 +78,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { srv := authinfoimpl.ProvideService( authInfoStore, remotecache.NewFakeCacheStorage(), secretsService) hs.authInfoService = srv - orgSvc, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotatest.New(false, nil)) + orgSvc, err := orgimpl.ProvideService(sqlStore, settings, quotatest.New(false, nil)) require.NoError(t, err) userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, sc.cfg, nil, nil, quotatest.New(false, nil), supportbundlestest.NewFakeBundleService()) require.NoError(t, err) @@ -148,7 +148,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { Login: "admin", IsAdmin: true, } - orgSvc, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotatest.New(false, nil)) + orgSvc, err := orgimpl.ProvideService(sqlStore, sc.cfg, quotatest.New(false, nil)) require.NoError(t, err) userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, sc.cfg, nil, nil, quotatest.New(false, nil), supportbundlestest.NewFakeBundleService()) require.NoError(t, err) @@ -379,8 +379,7 @@ func TestHTTPServer_UpdateUser(t *testing.T) { func setupUpdateEmailTests(t *testing.T, cfg *setting.Cfg) (*user.User, *HTTPServer, *notifications.NotificationServiceMock) { t.Helper() - sqlStore := db.InitTestDB(t) - sqlStore.Cfg = cfg + sqlStore := db.InitTestDB(t, sqlstore.InitTestDBOpt{Cfg: cfg}) tempUserService := tempuserimpl.ProvideService(sqlStore, cfg) orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotatest.New(false, nil)) @@ -606,8 +605,7 @@ func TestUser_UpdateEmail(t *testing.T) { } nsMock := notifications.MockNotificationService() - sqlStore := db.InitTestDB(t) - sqlStore.Cfg = settings + sqlStore := db.InitTestDB(t, sqlstore.InitTestDBOpt{Cfg: settings}) tempUserSvc := tempuserimpl.ProvideService(sqlStore, settings) orgSvc, err := orgimpl.ProvideService(sqlStore, settings, quotatest.New(false, nil)) diff --git a/pkg/cmd/grafana-cli/commands/conflict_user_command.go b/pkg/cmd/grafana-cli/commands/conflict_user_command.go index 5e188f6b797..faa8455867b 100644 --- a/pkg/cmd/grafana-cli/commands/conflict_user_command.go +++ b/pkg/cmd/grafana-cli/commands/conflict_user_command.go @@ -771,7 +771,7 @@ ORDER BY func notServiceAccount(ss *sqlstore.SQLStore) string { return fmt.Sprintf("is_service_account = %s", - ss.Dialect.BooleanStr(false)) + ss.GetDialect().BooleanStr(false)) } // confirm function asks for user input diff --git a/pkg/cmd/grafana-cli/commands/conflict_user_command_test.go b/pkg/cmd/grafana-cli/commands/conflict_user_command_test.go index 3d0f600dc88..00ee5325dae 100644 --- a/pkg/cmd/grafana-cli/commands/conflict_user_command_test.go +++ b/pkg/cmd/grafana-cli/commands/conflict_user_command_test.go @@ -109,9 +109,9 @@ func TestBuildConflictBlock(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { // Restore after destructive operation - sqlStore := db.InitTestDB(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) if sqlStore.GetDialect().DriverName() != ignoredDatabase { - userStore := userimpl.ProvideStore(sqlStore, sqlStore.Cfg) + userStore := userimpl.ProvideStore(sqlStore, cfg) for _, u := range tc.users { u := user.User{ Email: u.Email, @@ -217,9 +217,9 @@ conflict: test2 for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { // Restore after destructive operation - sqlStore := db.InitTestDB(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) if sqlStore.GetDialect().DriverName() != ignoredDatabase { - userStore := userimpl.ProvideStore(sqlStore, sqlStore.Cfg) + userStore := userimpl.ProvideStore(sqlStore, cfg) for _, u := range tc.users { u := user.User{ Email: u.Email, @@ -398,9 +398,9 @@ func TestGetConflictingUsers(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { // Restore after destructive operation - sqlStore := db.InitTestDB(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) if sqlStore.GetDialect().DriverName() != ignoredDatabase { - userStore := userimpl.ProvideStore(sqlStore, sqlStore.Cfg) + userStore := userimpl.ProvideStore(sqlStore, cfg) for _, u := range tc.users { u := user.User{ Email: u.Email, @@ -510,9 +510,9 @@ func TestGenerateConflictingUsersFile(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { // Restore after destructive operation - sqlStore := db.InitTestDB(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) if sqlStore.GetDialect().DriverName() != ignoredDatabase { - userStore := userimpl.ProvideStore(sqlStore, sqlStore.Cfg) + userStore := userimpl.ProvideStore(sqlStore, cfg) for _, u := range tc.users { cmd := user.User{ Email: u.Email, @@ -580,7 +580,7 @@ func TestRunValidateConflictUserFile(t *testing.T) { rawSQL := fmt.Sprintf( "INSERT INTO %s (email, login, org_id, version, is_admin, created, updated) VALUES (?,?,?,0,%s,\"2024-03-18T15:25:32\",\"2024-03-18T15:25:32\")", sqlStore.Quote("user"), - sqlStore.Dialect.BooleanStr(false), + sqlStore.GetDialect().BooleanStr(false), ) result, err := sess.Exec(rawSQL, dupUserLogincmd.Email, dupUserLogincmd.Login, dupUserLogincmd.OrgID) if err != nil { @@ -660,7 +660,7 @@ func TestIntegrationMergeUser(t *testing.T) { rawSQL := fmt.Sprintf( "INSERT INTO %s (email, login, org_id, version, is_admin, created, updated) VALUES (?,?,?,0,%s,?,?)", sqlStore.Quote("user"), - sqlStore.Dialect.BooleanStr(false), + sqlStore.GetDialect().BooleanStr(false), ) result, err := sess.Exec(rawSQL, cmd.Email, cmd.Login, cmd.OrgID, cmd.Created, cmd.Updated) if err != nil { @@ -692,7 +692,7 @@ func TestIntegrationMergeUser(t *testing.T) { rawSQL := fmt.Sprintf( "INSERT INTO %s (email, login, org_id, version, is_admin, created, updated) VALUES (?,?,?,0,%s,?,?)", sqlStore.Quote("user"), - sqlStore.Dialect.BooleanStr(false), + sqlStore.GetDialect().BooleanStr(false), ) result, err := sess.Exec(rawSQL, cmd.Email, cmd.Login, cmd.OrgID, cmd.Created, cmd.Updated) if err != nil { @@ -860,7 +860,7 @@ conflict: test2 rawSQL := fmt.Sprintf( "INSERT INTO %s (email, login, org_id, version, is_admin, created, updated) VALUES (?,?,?,0,%s,?,?)", sqlStore.Quote("user"), - sqlStore.Dialect.BooleanStr(false), + sqlStore.GetDialect().BooleanStr(false), ) result, err := sess.Exec(rawSQL, cmd.Email, cmd.Login, cmd.OrgID, cmd.Created, cmd.Updated) if err != nil { diff --git a/pkg/infra/db/db.go b/pkg/infra/db/db.go index 08cea4a761f..574354fa605 100644 --- a/pkg/infra/db/db.go +++ b/pkg/infra/db/db.go @@ -54,13 +54,16 @@ type Session = sqlstore.DBSession type InitTestDBOpt = sqlstore.InitTestDBOpt var SetupTestDB = sqlstore.SetupTestDB -var InitTestDB = sqlstore.InitTestDB var CleanupTestDB = sqlstore.CleanupTestDB var ProvideService = sqlstore.ProvideService -func InitTestDBwithCfg(t sqlutil.ITestDB, opts ...InitTestDBOpt) (*sqlstore.SQLStore, *setting.Cfg) { - store := InitTestDB(t, opts...) - return store, store.Cfg +func InitTestDB(t sqlutil.ITestDB, opts ...InitTestDBOpt) *sqlstore.SQLStore { + db, _ := InitTestDBWithCfg(t, opts...) + return db +} + +func InitTestDBWithCfg(t sqlutil.ITestDB, opts ...InitTestDBOpt) (*sqlstore.SQLStore, *setting.Cfg) { + return sqlstore.InitTestDB(t, opts...) } func IsTestDbSQLite() bool { diff --git a/pkg/infra/remotecache/remotecache_test.go b/pkg/infra/remotecache/remotecache_test.go index a51a836fa6c..b05d47fb3d7 100644 --- a/pkg/infra/remotecache/remotecache_test.go +++ b/pkg/infra/remotecache/remotecache_test.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/services/secrets/fakes" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tests/testsuite" ) @@ -33,15 +34,15 @@ func createTestClient(t *testing.T, opts *setting.RemoteCacheOptions, sqlstore d } func TestCachedBasedOnConfig(t *testing.T) { - cfg := setting.NewCfg() + db, cfg := sqlstore.InitTestDB(t) err := cfg.Load(setting.CommandLineArgs{ HomePath: "../../../", }) require.Nil(t, err, "Failed to load config") - client := createTestClient(t, cfg.RemoteCacheOptions, db.InitTestDB(t)) + client := createTestClient(t, cfg.RemoteCacheOptions, db) runTestsForClient(t, client) - runCountTestsForClient(t, cfg.RemoteCacheOptions, db.InitTestDB(t)) + runCountTestsForClient(t, cfg.RemoteCacheOptions, db) } func TestInvalidCacheTypeReturnsError(t *testing.T) { diff --git a/pkg/infra/usagestats/statscollector/concurrent_users_test.go b/pkg/infra/usagestats/statscollector/concurrent_users_test.go index 33ffcb3244a..decb1642657 100644 --- a/pkg/infra/usagestats/statscollector/concurrent_users_test.go +++ b/pkg/infra/usagestats/statscollector/concurrent_users_test.go @@ -24,7 +24,7 @@ func TestMain(m *testing.M) { } func TestConcurrentUsersMetrics(t *testing.T) { - sqlStore, cfg := db.InitTestDBwithCfg(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) statsService := statsimpl.ProvideService(&setting.Cfg{}, sqlStore) s := createService(t, cfg, sqlStore, statsService) @@ -42,7 +42,7 @@ func TestConcurrentUsersMetrics(t *testing.T) { } func TestConcurrentUsersStats(t *testing.T) { - sqlStore, cfg := db.InitTestDBwithCfg(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) statsService := statsimpl.ProvideService(&setting.Cfg{}, sqlStore) s := createService(t, cfg, sqlStore, statsService) diff --git a/pkg/server/module_server_test.go b/pkg/server/module_server_test.go index 23d8a89a20d..ecfb9c36d7a 100644 --- a/pkg/server/module_server_test.go +++ b/pkg/server/module_server_test.go @@ -33,8 +33,7 @@ func TestIntegrationWillRunInstrumentationServerWhenTargetHasNoHttpServer(t *tes t.Skip("skipping - sqlite not supported for storage server target") } - testdb := db.InitTestDB(t) - cfg := testdb.Cfg + _, cfg := db.InitTestDBWithCfg(t) cfg.GRPCServerNetwork = "tcp" cfg.GRPCServerAddress = "localhost:10000" addStorageServerToConfig(t, cfg, dbType) diff --git a/pkg/services/accesscontrol/database/database_test.go b/pkg/services/accesscontrol/database/database_test.go index 4c3c2e78aca..5f84c37387a 100644 --- a/pkg/services/accesscontrol/database/database_test.go +++ b/pkg/services/accesscontrol/database/database_test.go @@ -388,7 +388,7 @@ func createUsersAndTeams(t *testing.T, svcs helperServices, orgID int64, users [ } func setupTestEnv(t testing.TB) (*AccessControlStore, rs.Store, user.Service, team.Service, org.Service) { - sql, cfg := db.InitTestDBwithCfg(t) + sql, cfg := db.InitTestDBWithCfg(t) cfg.AutoAssignOrg = true cfg.AutoAssignOrgRole = "Viewer" cfg.AutoAssignOrgId = 1 diff --git a/pkg/services/accesscontrol/resourcepermissions/store_test.go b/pkg/services/accesscontrol/resourcepermissions/store_test.go index 5774e74adad..fc6647de5aa 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/store_test.go @@ -558,9 +558,9 @@ func seedResourcePermissions( } func setupTestEnv(t testing.TB) (*store, db.DB, *setting.Cfg) { - sql := db.InitTestDB(t) + sql, cfg := db.InitTestDBWithCfg(t) asService := NewActionSetService() - return NewStore(sql, featuremgmt.WithFeatures(), &asService), sql, sql.Cfg + return NewStore(sql, featuremgmt.WithFeatures(), &asService), sql, cfg } func TestStore_IsInherited(t *testing.T) { diff --git a/pkg/services/annotations/accesscontrol/accesscontrol_test.go b/pkg/services/annotations/accesscontrol/accesscontrol_test.go index f74bd3b945e..ad845b89967 100644 --- a/pkg/services/annotations/accesscontrol/accesscontrol_test.go +++ b/pkg/services/annotations/accesscontrol/accesscontrol_test.go @@ -27,8 +27,7 @@ func TestIntegrationAuthorize(t *testing.T) { t.Skip("skipping integration test") } - sql := db.InitTestDB(t) - cfg := sql.Cfg + sql, cfg := db.InitTestDBWithCfg(t) dash1 := testutil.CreateDashboard(t, sql, cfg, featuremgmt.WithFeatures(), dashboards.SaveDashboardCommand{ UserID: 1, diff --git a/pkg/services/annotations/annotationsimpl/annotations_test.go b/pkg/services/annotations/annotationsimpl/annotations_test.go index c96a5efb67d..8850cac78a8 100644 --- a/pkg/services/annotations/annotationsimpl/annotations_test.go +++ b/pkg/services/annotations/annotationsimpl/annotations_test.go @@ -209,14 +209,14 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) { annotationsTexts := make([]string, 0, folder.MaxNestedFolderDepth+1) setupFolderStructure := func() db.DB { - sql := db.InitTestDB(t) + sql, cfg := db.InitTestDBWithCfg(t) // enable nested folders so that the folder table is populated for all the tests features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders) tagService := tagimpl.ProvideService(sql) - dashStore, err := dashboardstore.ProvideDashboardStore(sql, sql.Cfg, features, tagService, quotatest.New(false, nil)) + dashStore, err := dashboardstore.ProvideDashboardStore(sql, cfg, features, tagService, quotatest.New(false, nil)) require.NoError(t, err) origNewGuardian := guardian.New @@ -225,10 +225,9 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) { guardian.New = origNewGuardian }) - ac := acimpl.ProvideAccessControl(sql.Cfg) - folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sql.Cfg, dashStore, folderimpl.ProvideDashboardFolderStore(sql), sql, features, supportbundlestest.NewFakeBundleService(), nil) + ac := acimpl.ProvideAccessControl(cfg) + folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashStore, folderimpl.ProvideDashboardFolderStore(sql), sql, features, supportbundlestest.NewFakeBundleService(), nil) - cfg := setting.NewCfg() cfg.AnnotationMaximumTagsLength = 60 store := NewXormStore(cfg, log.New("annotation.test"), sql, tagService) diff --git a/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go b/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go index a42256e0b4b..a364e9c48bc 100644 --- a/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go +++ b/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go @@ -42,8 +42,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) { t.Skip("skipping integration test") } - sql := db.InitTestDB(t) - cfg := sql.Cfg + sql, cfg := db.InitTestDBWithCfg(t) dashboard1 := testutil.CreateDashboard(t, sql, cfg, featuremgmt.WithFeatures(), dashboards.SaveDashboardCommand{ UserID: 1, diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index ae538afbd84..b640c050842 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -47,8 +47,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { var dashboardStore dashboards.Store setup := func() { - sql := db.InitTestDB(t) - sqlStore, cfg = sql, sql.Cfg + sqlStore, cfg = db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) var err error dashboardStore, err = ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore), quotaService) @@ -147,8 +146,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { var currentUser *user.SignedInUser setup2 := func() { - sql := db.InitTestDB(t) - sqlStore, cfg = sql, sql.Cfg + sqlStore, cfg = db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) var err error dashboardStore, err = ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore), quotaService) @@ -253,8 +251,7 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { var viewer *user.SignedInUser setup := func() { - sql := db.InitTestDB(t) - sqlStore, cfg = sql, sql.Cfg + sqlStore, cfg = db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) // enable nested folders so that the folder table is populated for all the tests diff --git a/pkg/services/dashboards/database/database_provisioning_test.go b/pkg/services/dashboards/database/database_provisioning_test.go index 4415cd77bec..798368370e9 100644 --- a/pkg/services/dashboards/database/database_provisioning_test.go +++ b/pkg/services/dashboards/database/database_provisioning_test.go @@ -18,9 +18,9 @@ func TestIntegrationDashboardProvisioningTest(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - sqlStore := db.InitTestDB(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore), quotaService) + dashboardStore, err := ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) folderCmd := dashboards.SaveDashboardCommand{ diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index 8a72e4374d5..2e76c05c1e6 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -49,7 +49,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { var dashboardStore dashboards.Store setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t) + sqlStore, cfg = db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) var err error dashboardStore, err = ProvideDashboardStore(sqlStore, cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore), quotaService) @@ -651,7 +651,7 @@ func TestIntegrationDashboard_Filter(t *testing.T) { }, Filters: []interface{}{ searchstore.TitleFilter{ - Dialect: sqlStore.Dialect, + Dialect: sqlStore.GetDialect(), Title: "Beta", }, }, @@ -711,9 +711,9 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) { orgID := int64(1) insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, "", false) - ac := acimpl.ProvideAccessControl(sqlStore.Cfg) + ac := acimpl.ProvideAccessControl(cfg) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) - folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) + folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) user := &user.SignedInUser{ OrgID: 1, @@ -828,9 +828,9 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) { orgID := int64(1) insertTestDashboard(t, dashboardStore, "dashboard under general", orgID, 0, "", false) - ac := acimpl.ProvideAccessControl(sqlStore.Cfg) + ac := acimpl.ProvideAccessControl(cfg) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) - folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) + folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) user := &user.SignedInUser{ OrgID: 1, diff --git a/pkg/services/folder/folderimpl/dashboard_folder_store_test.go b/pkg/services/folder/folderimpl/dashboard_folder_store_test.go index bfa481039f5..8345f6ce32a 100644 --- a/pkg/services/folder/folderimpl/dashboard_folder_store_test.go +++ b/pkg/services/folder/folderimpl/dashboard_folder_store_test.go @@ -28,7 +28,7 @@ func TestIntegrationDashboardFolderStore(t *testing.T) { var dashboardStore dashboards.Store setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t) + sqlStore, cfg = db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) var err error dashboardStore, err = database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch), tagimpl.ProvideService(sqlStore), quotaService) @@ -40,7 +40,7 @@ func TestIntegrationDashboardFolderStore(t *testing.T) { title := "Very Unique Name" var sqlStore db.DB var folder1, folder2 *dashboards.Dashboard - sqlStore = db.InitTestDB(t) + sqlStore, cfg = db.InitTestDBWithCfg(t) folderStore := ProvideDashboardFolderStore(sqlStore) folder2 = insertTestFolder(t, dashboardStore, "TEST", orgId, "", "prod") _ = insertTestDashboard(t, dashboardStore, title, orgId, folder2.ID, folder2.UID, "prod") diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 56ab7e128a3..5abe492742c 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -58,7 +58,7 @@ func TestIntegrationProvideFolderService(t *testing.T) { t.Run("should register scope resolvers", func(t *testing.T) { cfg := setting.NewCfg() ac := acmock.New() - db := sqlstore.InitTestDB(t) + db := db.InitTestDB(t) ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil) require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 3) @@ -71,12 +71,11 @@ func TestIntegrationFolderService(t *testing.T) { } t.Run("Folder service tests", func(t *testing.T) { dashStore := &dashboards.FakeDashboardStore{} - db := sqlstore.InitTestDB(t) - nestedFolderStore := ProvideStore(db, db.Cfg) + db, cfg := sqlstore.InitTestDB(t) + nestedFolderStore := ProvideStore(db, cfg) folderStore := foldertest.NewFakeFolderStore(t) - cfg := setting.NewCfg() features := featuremgmt.WithFeatures() ac := acmock.New().WithPermissions([]accesscontrol.Permission{ @@ -354,16 +353,14 @@ func TestIntegrationNestedFolderService(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - db := sqlstore.InitTestDB(t) + db, cfg := sqlstore.InitTestDB(t) quotaService := quotatest.New(false, nil) folderStore := ProvideDashboardFolderStore(db) - cfg := setting.NewCfg() - featuresFlagOn := featuremgmt.WithFeatures("nestedFolders") - dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOn, tagimpl.ProvideService(db), quotaService) + dashStore, err := database.ProvideDashboardStore(db, cfg, featuresFlagOn, tagimpl.ProvideService(db), quotaService) require.NoError(t, err) - nestedFolderStore := ProvideStore(db, db.Cfg) + nestedFolderStore := ProvideStore(db, cfg) b := bus.ProvideBus(tracing.InitializeTracerForTest()) ac := acimpl.ProvideAccessControl(cfg) @@ -479,9 +476,9 @@ func TestIntegrationNestedFolderService(t *testing.T) { }) t.Run("With nested folder feature flag off", func(t *testing.T) { featuresFlagOff := featuremgmt.WithFeatures() - dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOff, tagimpl.ProvideService(db), quotaService) + dashStore, err := database.ProvideDashboardStore(db, cfg, featuresFlagOff, tagimpl.ProvideService(db), quotaService) require.NoError(t, err) - nestedFolderStore := ProvideStore(db, db.Cfg) + nestedFolderStore := ProvideStore(db, cfg) serviceWithFlagOff := &Service{ cfg: cfg, @@ -644,9 +641,9 @@ func TestIntegrationNestedFolderService(t *testing.T) { lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, tc.service) require.NoError(t, err) - dashStore, err := database.ProvideDashboardStore(db, db.Cfg, tc.featuresFlag, tagimpl.ProvideService(db), quotaService) + dashStore, err := database.ProvideDashboardStore(db, cfg, tc.featuresFlag, tagimpl.ProvideService(db), quotaService) require.NoError(t, err) - nestedFolderStore := ProvideStore(db, db.Cfg) + nestedFolderStore := ProvideStore(db, cfg) tc.service.dashboardStore = dashStore tc.service.store = nestedFolderStore @@ -737,11 +734,11 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) { dashboardFolderStore := foldertest.NewFakeFolderStore(t) - cfg := setting.NewCfg() + db, cfg := sqlstore.InitTestDB(t) folderService := &Service{ cfg: cfg, store: nestedFolderStore, - db: sqlstore.InitTestDB(t), + db: db, dashboardStore: &dashStore, dashboardFolderStore: dashboardFolderStore, features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders), @@ -765,7 +762,7 @@ func TestFolderServiceDualWrite(t *testing.T) { guardian.New = g }) - db := sqlstore.InitTestDB(t) + db, _ := sqlstore.InitTestDB(t) cfg := setting.NewCfg() features := featuremgmt.WithFeatures() nestedFolderStore := ProvideStore(db, cfg) @@ -778,7 +775,7 @@ func TestFolderServiceDualWrite(t *testing.T) { folderService := &Service{ cfg: setting.NewCfg(), store: nestedFolderStore, - db: sqlstore.InitTestDB(t), + db: db, dashboardStore: dashStore, dashboardFolderStore: dashboardFolderStore, features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders), @@ -845,7 +842,8 @@ func TestNestedFolderService(t *testing.T) { nestedFolderStore := NewFakeStore() - folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures(), acimpl.ProvideAccessControl(setting.NewCfg()), sqlstore.InitTestDB(t)) + db, _ := sqlstore.InitTestDB(t) + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures(), acimpl.ProvideAccessControl(setting.NewCfg()), db) _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, Title: dash.Title, @@ -877,7 +875,8 @@ func TestNestedFolderService(t *testing.T) { dashboardFolderStore := foldertest.NewFakeFolderStore(t) nestedFolderStore := NewFakeStore() - folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), acimpl.ProvideAccessControl(setting.NewCfg()), sqlstore.InitTestDB(t)) + db, _ := sqlstore.InitTestDB(t) + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), acimpl.ProvideAccessControl(setting.NewCfg()), db) _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, Title: dash.Title, @@ -942,7 +941,8 @@ func TestNestedFolderService(t *testing.T) { nestedFolderUser.Permissions[orgID] = map[string][]string{dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersProvider.GetResourceScopeUID("some_parent")}} nestedFolderStore := NewFakeStore() - folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), acimpl.ProvideAccessControl(setting.NewCfg()), sqlstore.InitTestDB(t)) + db, _ := sqlstore.InitTestDB(t) + folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), acimpl.ProvideAccessControl(setting.NewCfg()), db) _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, Title: dash.Title, @@ -969,9 +969,10 @@ func TestNestedFolderService(t *testing.T) { dashboardFolderStore := foldertest.NewFakeFolderStore(t) nestedFolderStore := NewFakeStore() + db, _ := sqlstore.InitTestDB(t) folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, - }, sqlstore.InitTestDB(t)) + }, db) f, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, Title: "myFolder", @@ -1019,9 +1020,10 @@ func TestNestedFolderService(t *testing.T) { SignedInUser: usr, } + db, _ := sqlstore.InitTestDB(t) folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, - }, sqlstore.InitTestDB(t)) + }, db) _, err := folderSvc.Create(context.Background(), &cmd) require.Error(t, err, folder.ErrCircularReference) // CreateFolder should not call the folder store's create method. @@ -1048,9 +1050,10 @@ func TestNestedFolderService(t *testing.T) { nestedFolderStore.ExpectedError = errors.New("FAILED") // the service return success as long as the legacy create succeeds + db, _ := sqlstore.InitTestDB(t) folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, - }, sqlstore.InitTestDB(t)) + }, db) _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, Title: "myFolder", @@ -1232,9 +1235,10 @@ func TestNestedFolderService(t *testing.T) { //nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} nestedFolderStore.ExpectedParentFolders = parents + db, _ := sqlstore.InitTestDB(t) folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ ExpectedEvaluate: true, - }, sqlstore.InitTestDB(t)) + }, db) _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ Title: "folder", OrgID: orgID, @@ -1277,16 +1281,14 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - db := sqlstore.InitTestDB(t) + db, cfg := sqlstore.InitTestDB(t) quotaService := quotatest.New(false, nil) folderStore := ProvideDashboardFolderStore(db) - cfg := setting.NewCfg() - featuresFlagOn := featuremgmt.WithFeatures("nestedFolders") - dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOn, tagimpl.ProvideService(db), quotaService) + dashStore, err := database.ProvideDashboardStore(db, cfg, featuresFlagOn, tagimpl.ProvideService(db), quotaService) require.NoError(t, err) - nestedFolderStore := ProvideStore(db, db.Cfg) + nestedFolderStore := ProvideStore(db, cfg) b := bus.ProvideBus(tracing.InitializeTracerForTest()) ac := acimpl.ProvideAccessControl(cfg) @@ -1631,7 +1633,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { } func TestFolderServiceGetFolder(t *testing.T) { - db := sqlstore.InitTestDB(t) + db, _ := sqlstore.InitTestDB(t) signedInAdminUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{ orgID: { @@ -1653,9 +1655,9 @@ func TestFolderServiceGetFolder(t *testing.T) { cfg := setting.NewCfg() featuresFlagOff := featuremgmt.WithFeatures() - dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOff, tagimpl.ProvideService(db), quotaService) + dashStore, err := database.ProvideDashboardStore(db, cfg, featuresFlagOff, tagimpl.ProvideService(db), quotaService) require.NoError(t, err) - nestedFolderStore := ProvideStore(db, db.Cfg) + nestedFolderStore := ProvideStore(db, cfg) b := bus.ProvideBus(tracing.InitializeTracerForTest()) ac := acimpl.ProvideAccessControl(cfg) @@ -1731,16 +1733,14 @@ func TestFolderServiceGetFolder(t *testing.T) { } func TestFolderServiceGetFolders(t *testing.T) { - db := sqlstore.InitTestDB(t) + db, cfg := sqlstore.InitTestDB(t) 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) + dashStore, err := database.ProvideDashboardStore(db, cfg, featuresFlagOff, tagimpl.ProvideService(db), quotaService) require.NoError(t, err) - nestedFolderStore := ProvideStore(db, db.Cfg) + nestedFolderStore := ProvideStore(db, cfg) b := bus.ProvideBus(tracing.InitializeTracerForTest()) ac := acimpl.ProvideAccessControl(cfg) @@ -1809,7 +1809,7 @@ func TestFolderServiceGetFolders(t *testing.T) { // TODO replace it with an API test under /pkg/tests/api/folders // whenever the golang client with get updated to allow filtering child folders by permission func TestGetChildrenFilterByPermission(t *testing.T) { - db := sqlstore.InitTestDB(t) + db, cfg := sqlstore.InitTestDB(t) signedInAdminUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{ orgID: { @@ -1822,12 +1822,10 @@ func TestGetChildrenFilterByPermission(t *testing.T) { 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) + dashStore, err := database.ProvideDashboardStore(db, cfg, featuresFlagOff, tagimpl.ProvideService(db), quotaService) require.NoError(t, err) - nestedFolderStore := ProvideStore(db, db.Cfg) + nestedFolderStore := ProvideStore(db, cfg) b := bus.ProvideBus(tracing.InitializeTracerForTest()) ac := acimpl.ProvideAccessControl(cfg) diff --git a/pkg/services/folder/folderimpl/sqlstore_test.go b/pkg/services/folder/folderimpl/sqlstore_test.go index 9f54c152653..5c33c3d935b 100644 --- a/pkg/services/folder/folderimpl/sqlstore_test.go +++ b/pkg/services/folder/folderimpl/sqlstore_test.go @@ -31,10 +31,10 @@ func TestIntegrationCreate(t *testing.T) { t.Skip("skipping integration test") } - db := sqlstore.InitTestDB(t) - folderStore := ProvideStore(db, db.Cfg) + db, cfg := sqlstore.InitTestDB(t) + folderStore := ProvideStore(db, cfg) - orgID := CreateOrg(t, db, db.Cfg) + orgID := CreateOrg(t, db, cfg) t.Run("creating a folder without providing a UID should fail", func(t *testing.T) { _, err := folderStore.Create(context.Background(), folder.CreateFolderCommand{ @@ -151,10 +151,10 @@ func TestIntegrationDelete(t *testing.T) { t.Skip("skipping integration test") } - db := sqlstore.InitTestDB(t) - folderStore := ProvideStore(db, db.Cfg) + db, cfg := sqlstore.InitTestDB(t) + folderStore := ProvideStore(db, cfg) - orgID := CreateOrg(t, db, db.Cfg) + orgID := CreateOrg(t, db, cfg) /* t.Run("attempt to delete unknown folder should fail", func(t *testing.T) { @@ -198,10 +198,10 @@ func TestIntegrationUpdate(t *testing.T) { t.Skip("skipping integration test") } - db := sqlstore.InitTestDB(t) - folderStore := ProvideStore(db, db.Cfg) + db, cfg := sqlstore.InitTestDB(t) + folderStore := ProvideStore(db, cfg) - orgID := CreateOrg(t, db, db.Cfg) + orgID := CreateOrg(t, db, cfg) // create parent folder parent, err := folderStore.Create(context.Background(), folder.CreateFolderCommand{ @@ -373,10 +373,10 @@ func TestIntegrationGet(t *testing.T) { t.Skip("skipping integration test") } - db := sqlstore.InitTestDB(t) - folderStore := ProvideStore(db, db.Cfg) + db, cfg := sqlstore.InitTestDB(t) + folderStore := ProvideStore(db, cfg) - orgID := CreateOrg(t, db, db.Cfg) + orgID := CreateOrg(t, db, cfg) // create folder uid1 := util.GenerateShortUID() @@ -490,10 +490,10 @@ func TestIntegrationGetParents(t *testing.T) { t.Skip("skipping integration test") } - db := sqlstore.InitTestDB(t) - folderStore := ProvideStore(db, db.Cfg) + db, cfg := sqlstore.InitTestDB(t) + folderStore := ProvideStore(db, cfg) - orgID := CreateOrg(t, db, db.Cfg) + orgID := CreateOrg(t, db, cfg) // create folder uid1 := util.GenerateShortUID() @@ -558,10 +558,10 @@ func TestIntegrationGetChildren(t *testing.T) { t.Skip("skipping integration test") } - db := sqlstore.InitTestDB(t) - folderStore := ProvideStore(db, db.Cfg) + db, cfg := sqlstore.InitTestDB(t) + folderStore := ProvideStore(db, cfg) - orgID := CreateOrg(t, db, db.Cfg) + orgID := CreateOrg(t, db, cfg) // create folder uid1 := util.GenerateShortUID() @@ -738,10 +738,10 @@ func TestIntegrationGetHeight(t *testing.T) { t.Skip("skipping integration test") } - db := sqlstore.InitTestDB(t) - folderStore := ProvideStore(db, db.Cfg) + db, cfg := sqlstore.InitTestDB(t) + folderStore := ProvideStore(db, cfg) - orgID := CreateOrg(t, db, db.Cfg) + orgID := CreateOrg(t, db, cfg) // create folder uid1 := util.GenerateShortUID() @@ -771,10 +771,10 @@ func TestIntegrationGetFolders(t *testing.T) { } foldersNum := 10 - db := sqlstore.InitTestDB(t) - folderStore := ProvideStore(db, db.Cfg) + db, cfg := sqlstore.InitTestDB(t) + folderStore := ProvideStore(db, cfg) - orgID := CreateOrg(t, db, db.Cfg) + orgID := CreateOrg(t, db, cfg) // create folders uids := make([]string, 0) diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 189ccfac350..b7fec91dac9 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -379,22 +379,22 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena t.Helper() features := featuremgmt.WithFeatures() - sqlStore := db.InitTestDB(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) ac := actest.FakeAccessControl{} quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, features, tagimpl.ProvideService(sqlStore), quotaService) + dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) folderPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService() folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) dashboardService, svcErr := dashboardservice.ProvideDashboardServiceImpl( - sqlStore.Cfg, dashboardStore, folderStore, + cfg, dashboardStore, folderStore, features, folderPermissions, dashboardPermissions, ac, foldertest.NewFakeService(), nil, ) require.NoError(t, svcErr) - guardian.InitAccessControlGuardian(sqlStore.Cfg, ac, dashboardService) + guardian.InitAccessControlGuardian(cfg, ac, dashboardService) testScenario(t, desc, func(t *testing.T, sc scenarioContext) { // nolint:staticcheck @@ -439,28 +439,28 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo webCtx := web.Context{Req: req} features := featuremgmt.WithFeatures() - sqlStore := db.InitTestDB(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) - dashboardStore, err := database.ProvideDashboardStore(sqlStore, sqlStore.Cfg, features, tagimpl.ProvideService(sqlStore), quotaService) + dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) - ac := acimpl.ProvideAccessControl(sqlStore.Cfg) + ac := acimpl.ProvideAccessControl(cfg) folderPermissions := acmock.NewMockedPermissionsService() folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) dashboardPermissions := acmock.NewMockedPermissionsService() folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) dashService, dashSvcErr := dashboardservice.ProvideDashboardServiceImpl( - sqlStore.Cfg, dashboardStore, folderStore, + cfg, dashboardStore, folderStore, features, folderPermissions, dashboardPermissions, ac, foldertest.NewFakeService(), nil, ) require.NoError(t, dashSvcErr) - guardian.InitAccessControlGuardian(sqlStore.Cfg, ac, dashService) + guardian.InitAccessControlGuardian(cfg, ac, dashService) service := LibraryElementService{ - Cfg: sqlStore.Cfg, + Cfg: cfg, features: featuremgmt.WithFeatures(), SQLStore: sqlStore, - folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil), + folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil), } // deliberate difference between signed in user and user in db to make it crystal clear @@ -471,9 +471,9 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo Name: "User In DB", Login: userInDbName, } - orgSvc, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService) + orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sqlStore, orgSvc, sqlStore.Cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) require.NoError(t, err) _, err = usrSvc.Create(context.Background(), &cmd) require.NoError(t, err) diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index ab36bda9fa1..2ff4d3f84f3 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -815,7 +815,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo t.Run(desc, func(t *testing.T) { orgID := int64(1) role := org.RoleAdmin - sqlStore, cfg := db.InitTestDBwithCfg(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) ac := actest.FakeAccessControl{ExpectedEvaluate: true} @@ -870,9 +870,9 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo Login: userInDbName, } ctx := appcontext.WithUser(context.Background(), usr) - orgSvc, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService) + orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sqlStore, orgSvc, sqlStore.Cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) require.NoError(t, err) _, err = usrSvc.Create(context.Background(), &cmd) require.NoError(t, err) diff --git a/pkg/services/org/orgimpl/org.go b/pkg/services/org/orgimpl/org.go index f9395027cdf..47ecc8d44e1 100644 --- a/pkg/services/org/orgimpl/org.go +++ b/pkg/services/org/orgimpl/org.go @@ -27,7 +27,6 @@ func ProvideService(db db.DB, cfg *setting.Cfg, quotaService quota.Service) (org db: db, dialect: db.GetDialect(), log: log, - cfg: cfg, }, cfg: cfg, log: log, diff --git a/pkg/services/org/orgimpl/store.go b/pkg/services/org/orgimpl/store.go index 59eb2dc1016..2d987b1539e 100644 --- a/pkg/services/org/orgimpl/store.go +++ b/pkg/services/org/orgimpl/store.go @@ -17,7 +17,6 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -54,7 +53,6 @@ type sqlStore struct { dialect migrator.Dialect //TODO: moved to service log log.Logger - cfg *setting.Cfg deletes []string } diff --git a/pkg/services/org/orgimpl/store_test.go b/pkg/services/org/orgimpl/store_test.go index 1c62a471641..37c9f580d21 100644 --- a/pkg/services/org/orgimpl/store_test.go +++ b/pkg/services/org/orgimpl/store_test.go @@ -257,7 +257,6 @@ func TestIntegrationOrgUserDataAccess(t *testing.T) { orgUserStore := sqlStore{ db: ss, dialect: ss.GetDialect(), - cfg: setting.NewCfg(), } t.Run("org user inserted", func(t *testing.T) { @@ -330,8 +329,8 @@ func TestIntegrationOrgUserDataAccess(t *testing.T) { require.NoError(t, err) }) t.Run("GetOrgUsers and UpdateOrgUsers", func(t *testing.T) { - ss := db.InitTestDB(t) - _, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg) + ss, cfg := db.InitTestDBWithCfg(t) + _, usrSvc := createOrgAndUserSvc(t, ss, cfg) ac1cmd := &user.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"} ac2cmd := &user.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name", IsAdmin: true} ac1, err := usrSvc.Create(context.Background(), ac1cmd) @@ -475,12 +474,12 @@ func TestIntegrationOrgUserDataAccess(t *testing.T) { }) t.Run("Given single org and 2 users inserted", func(t *testing.T) { - ss = db.InitTestDB(t) - ss.Cfg.AutoAssignOrg = true - ss.Cfg.AutoAssignOrgId = 1 - ss.Cfg.AutoAssignOrgRole = "Viewer" + ss, cfg := db.InitTestDBWithCfg(t) + cfg.AutoAssignOrg = true + cfg.AutoAssignOrgId = 1 + cfg.AutoAssignOrgRole = "Viewer" - orgSvc, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg) + orgSvc, usrSvc := createOrgAndUserSvc(t, ss, cfg) testUser := &user.SignedInUser{ Permissions: map[int64]map[string][]string{ @@ -535,16 +534,18 @@ func TestIntegrationSQLStore_AddOrgUser(t *testing.T) { t.Skip("skipping integration test") } - store := db.InitTestDB(t) - store.Cfg.AutoAssignOrg = true - store.Cfg.AutoAssignOrgId = 1 - store.Cfg.AutoAssignOrgRole = "Viewer" + store, cfg := db.InitTestDBWithCfg(t) + defer func() { + cfg.AutoAssignOrg, cfg.AutoAssignOrgId, cfg.AutoAssignOrgRole = false, 0, "" + }() + cfg.AutoAssignOrg = true + cfg.AutoAssignOrgId = 1 + cfg.AutoAssignOrgRole = "Viewer" orgUserStore := sqlStore{ db: store, dialect: store.GetDialect(), - cfg: setting.NewCfg(), } - orgSvc, usrSvc := createOrgAndUserSvc(t, store, store.Cfg) + orgSvc, usrSvc := createOrgAndUserSvc(t, store, cfg) o, err := orgSvc.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "test org"}) require.NoError(t, err) @@ -604,19 +605,17 @@ func TestIntegration_SQLStore_GetOrgUsers(t *testing.T) { t.Skip("skipping integration test") } - store := db.InitTestDB(t) + store, cfg := db.InitTestDBWithCfg(t) orgUserStore := sqlStore{ db: store, dialect: store.GetDialect(), - cfg: setting.NewCfg(), } - orgUserStore.cfg.IsEnterprise = true + cfg.IsEnterprise = true defer func() { - orgUserStore.cfg.IsEnterprise = false + cfg.IsEnterprise = false }() - store.Cfg = setting.NewCfg() - orgSvc, userSvc := createOrgAndUserSvc(t, store, store.Cfg) + orgSvc, userSvc := createOrgAndUserSvc(t, store, cfg) o, err := orgSvc.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "test org"}) require.NoError(t, err) @@ -724,13 +723,12 @@ func TestIntegration_SQLStore_GetOrgUsers_PopulatesCorrectly(t *testing.T) { userimpl.MockTimeNow(constNow) defer userimpl.ResetTimeNow() - store := db.InitTestDB(t, sqlstore.InitTestDBOpt{}) + store, cfg := db.InitTestDBWithCfg(t, sqlstore.InitTestDBOpt{}) orgUserStore := sqlStore{ db: store, dialect: store.GetDialect(), - cfg: setting.NewCfg(), } - _, usrSvc := createOrgAndUserSvc(t, store, store.Cfg) + _, usrSvc := createOrgAndUserSvc(t, store, cfg) id, err := orgUserStore.Insert(context.Background(), &org.Org{ @@ -786,14 +784,13 @@ func TestIntegration_SQLStore_SearchOrgUsers(t *testing.T) { t.Skip("skipping integration test") } - store := db.InitTestDB(t, sqlstore.InitTestDBOpt{}) + store, cfg := db.InitTestDBWithCfg(t, sqlstore.InitTestDBOpt{}) orgUserStore := sqlStore{ db: store, dialect: store.GetDialect(), - cfg: setting.NewCfg(), } // orgUserStore.cfg.Skip - orgSvc, userSvc := createOrgAndUserSvc(t, store, store.Cfg) + orgSvc, userSvc := createOrgAndUserSvc(t, store, cfg) o, err := orgSvc.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "test org"}) require.NoError(t, err) @@ -863,13 +860,12 @@ func TestIntegration_SQLStore_RemoveOrgUser(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - store := db.InitTestDB(t) + store, cfg := db.InitTestDBWithCfg(t) orgUserStore := sqlStore{ db: store, dialect: store.GetDialect(), - cfg: setting.NewCfg(), } - orgSvc, usrSvc := createOrgAndUserSvc(t, store, store.Cfg) + orgSvc, usrSvc := createOrgAndUserSvc(t, store, cfg) o, err := orgSvc.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: MainOrgName}) require.NoError(t, err) diff --git a/pkg/services/pluginsintegration/plugins_integration_test.go b/pkg/services/pluginsintegration/plugins_integration_test.go index 726b47f8e8f..27ec459cd84 100644 --- a/pkg/services/pluginsintegration/plugins_integration_test.go +++ b/pkg/services/pluginsintegration/plugins_integration_test.go @@ -89,7 +89,8 @@ func TestIntegrationPluginManager(t *testing.T) { pg := postgres.ProvideService(cfg) my := mysql.ProvideService() ms := mssql.ProvideService(cfg) - sv2 := searchV2.ProvideService(cfg, db.InitTestDB(t, sqlstore.InitTestDBOpt{Cfg: cfg}), nil, nil, tracer, features, nil, nil, nil) + db := db.InitTestDB(t, sqlstore.InitTestDBOpt{Cfg: cfg}) + sv2 := searchV2.ProvideService(cfg, db, nil, nil, tracer, features, nil, nil, nil) graf := grafanads.ProvideService(sv2, nil) pyroscope := pyroscope.ProvideService(hcp) parca := parca.ProvideService(hcp) diff --git a/pkg/services/publicdashboards/api/query_test.go b/pkg/services/publicdashboards/api/query_test.go index b92f142a1e0..385fe19dc47 100644 --- a/pkg/services/publicdashboards/api/query_test.go +++ b/pkg/services/publicdashboards/api/query_test.go @@ -40,7 +40,6 @@ import ( "github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/web" ) @@ -258,7 +257,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) if testing.Short() { t.Skip("skipping integration test") } - db := db.InitTestDB(t) + db, cfg := db.InitTestDBWithCfg(t) cacheService := datasourcesService.ProvideCacheService(localcache.ProvideService(), db, guardian.ProvideGuardian()) qds := buildQueryDataService(t, cacheService, nil, db) @@ -300,7 +299,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) } // create dashboard - dashboardStoreService, err := dashboardStore.ProvideDashboardStore(db, db.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db), quotatest.New(false, nil)) + dashboardStoreService, err := dashboardStore.ProvideDashboardStore(db, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(db), quotatest.New(false, nil)) require.NoError(t, err) dashboard, err := dashboardStoreService.SaveDashboard(context.Background(), saveDashboardCmd) require.NoError(t, err) @@ -318,8 +317,7 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) annotationsService := annotationstest.NewFakeAnnotationsRepo() // create public dashboard - store := publicdashboardsStore.ProvideStore(db, db.Cfg, featuremgmt.WithFeatures()) - cfg := setting.NewCfg() + store := publicdashboardsStore.ProvideStore(db, cfg, featuremgmt.WithFeatures()) cfg.PublicDashboardsEnabled = true ac := acmock.New() ws := publicdashboardsService.ProvideServiceWrapper(store) diff --git a/pkg/services/publicdashboards/database/database_test.go b/pkg/services/publicdashboards/database/database_test.go index 8108bab714d..fd8273cc198 100644 --- a/pkg/services/publicdashboards/database/database_test.go +++ b/pkg/services/publicdashboards/database/database_test.go @@ -63,7 +63,7 @@ func TestIntegrationListPublicDashboard(t *testing.T) { var publicdashboardStore *PublicDashboardStoreImpl setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{}) + sqlStore, cfg = db.InitTestDBWithCfg(t, db.InitTestDBOpt{}) quotaService := quotatest.New(false, nil) dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) @@ -178,7 +178,7 @@ func TestIntegrationExistsEnabledByAccessToken(t *testing.T) { var savedDashboard *dashboards.Dashboard setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t) + sqlStore, cfg = db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) @@ -251,7 +251,7 @@ func TestIntegrationExistsEnabledByDashboardUid(t *testing.T) { var savedDashboard *dashboards.Dashboard setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t) + sqlStore, cfg = db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) @@ -316,7 +316,7 @@ func TestIntegrationFindByDashboardUid(t *testing.T) { var savedDashboard *dashboards.Dashboard setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t) + sqlStore, cfg = db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) @@ -385,7 +385,7 @@ func TestIntegrationFindByAccessToken(t *testing.T) { var err error setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t) + sqlStore, cfg = db.InitTestDBWithCfg(t) dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotatest.New(false, nil)) require.NoError(t, err) publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) @@ -453,7 +453,7 @@ func TestIntegrationCreatePublicDashboard(t *testing.T) { var savedDashboard2 *dashboards.Dashboard setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{}) + sqlStore, cfg = db.InitTestDBWithCfg(t, db.InitTestDBOpt{}) quotaService := quotatest.New(false, nil) store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) @@ -533,7 +533,7 @@ func TestIntegrationUpdatePublicDashboard(t *testing.T) { var err error setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{}) + sqlStore, cfg = db.InitTestDBWithCfg(t, db.InitTestDBOpt{}) quotaService := quotatest.New(false, nil) dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) @@ -637,7 +637,7 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) { var err error setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t) + sqlStore, cfg = db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) @@ -710,7 +710,7 @@ func TestIntegrationDelete(t *testing.T) { var err error setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t) + sqlStore, cfg = db.InitTestDBWithCfg(t) dashboardStore, err = dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotatest.New(false, nil)) require.NoError(t, err) publicdashboardStore = ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) @@ -742,9 +742,9 @@ func TestIntegrationDelete(t *testing.T) { func TestFindByFolder(t *testing.T) { t.Run("returns nil when dashboard is not a folder", func(t *testing.T) { - sqlStore, _ := db.InitTestDBwithCfg(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) dashboard := &dashboards.Dashboard{OrgID: 1, UID: "dashboarduid", IsFolder: false} - store := ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures()) + store := ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) pubdashes, err := store.FindByFolder(context.Background(), dashboard.OrgID, dashboard.UID) require.NoError(t, err) @@ -752,8 +752,8 @@ func TestFindByFolder(t *testing.T) { }) t.Run("returns nil when parameters are empty", func(t *testing.T) { - sqlStore, _ := db.InitTestDBwithCfg(t) - store := ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures()) + sqlStore, cfg := db.InitTestDBWithCfg(t) + store := ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) pubdashes, err := store.FindByFolder(context.Background(), 0, "") require.NoError(t, err) @@ -761,11 +761,11 @@ func TestFindByFolder(t *testing.T) { }) t.Run("can get all pubdashes for dashboard folder and org", func(t *testing.T) { - sqlStore, _ := db.InitTestDBwithCfg(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) + dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) - pubdashStore := ProvideStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures()) + pubdashStore := ProvideStore(sqlStore, cfg, featuremgmt.WithFeatures()) // insert folders folder := insertTestDashboard(t, dashboardStore, "This is a folder", 1, "", true, PublicShareType) folder2 := insertTestDashboard(t, dashboardStore, "This is another folder", 1, "", true, PublicShareType) @@ -800,7 +800,7 @@ func TestGetMetrics(t *testing.T) { var savedDashboard4 *dashboards.Dashboard setup := func() { - sqlStore, cfg = db.InitTestDBwithCfg(t, db.InitTestDBOpt{}) + sqlStore, cfg = db.InitTestDBWithCfg(t, db.InitTestDBOpt{}) quotaService := quotatest.New(false, nil) store, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) diff --git a/pkg/services/publicdashboards/service/common_test.go b/pkg/services/publicdashboards/service/common_test.go index da383640f65..1124b687288 100644 --- a/pkg/services/publicdashboards/service/common_test.go +++ b/pkg/services/publicdashboards/service/common_test.go @@ -14,7 +14,6 @@ import ( "github.com/grafana/grafana/pkg/services/publicdashboards/database" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" "github.com/grafana/grafana/pkg/services/publicdashboards/service/intervalv2" - "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/setting" ) @@ -27,14 +26,14 @@ func newPublicDashboardServiceImpl( ) (*PublicDashboardServiceImpl, db.DB, *setting.Cfg) { t.Helper() - db := sqlstore.InitTestDB(t) + db, cfg := db.InitTestDBWithCfg(t) tagService := tagimpl.ProvideService(db) if annotationsRepo == nil { - annotationsRepo = annotationsimpl.ProvideService(db, db.Cfg, featuremgmt.WithFeatures(), tagService) + annotationsRepo = annotationsimpl.ProvideService(db, cfg, featuremgmt.WithFeatures(), tagService) } if publicDashboardStore == nil { - publicDashboardStore = database.ProvideStore(db, db.Cfg, featuremgmt.WithFeatures()) + publicDashboardStore = database.ProvideStore(db, cfg, featuremgmt.WithFeatures()) } serviceWrapper := ProvideServiceWrapper(publicDashboardStore) @@ -50,5 +49,5 @@ func newPublicDashboardServiceImpl( serviceWrapper: serviceWrapper, license: license, features: featuremgmt.WithFeatures(), - }, db, db.Cfg + }, db, cfg } diff --git a/pkg/services/publicdashboards/service/query_test.go b/pkg/services/publicdashboards/service/query_test.go index 3aada6964bb..b53bb878603 100644 --- a/pkg/services/publicdashboards/service/query_test.go +++ b/pkg/services/publicdashboards/service/query_test.go @@ -1320,8 +1320,8 @@ func TestBuildMetricRequest(t *testing.T) { } func TestBuildAnonymousUser(t *testing.T) { - sqlStore := db.InitTestDB(t) - dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, sqlStore.Cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotatest.New(false, nil)) + sqlStore, cfg := db.InitTestDBWithCfg(t) + dashboardStore, err := dashboardsDB.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotatest.New(false, nil)) require.NoError(t, err) dashboard := insertTestDashboard(t, dashboardStore, "testDashie", 1, 0, "", true, []map[string]interface{}{}, nil) features := featuremgmt.WithFeatures() diff --git a/pkg/services/query/query_test.go b/pkg/services/query/query_test.go index fdaf22fbcd4..2df66a19e8b 100644 --- a/pkg/services/query/query_test.go +++ b/pkg/services/query/query_test.go @@ -465,7 +465,7 @@ func setup(t *testing.T) *testContext { dc := &fakeDataSourceCache{cache: dss} rv := &fakePluginRequestValidator{} - sqlStore := db.InitTestDB(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) ss := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) fakeDatasourceService := &fakeDatasources.FakeDataSourceService{ @@ -473,7 +473,7 @@ func setup(t *testing.T) *testContext { SimulatePluginFailure: false, } - pCtxProvider := plugincontext.ProvideService(sqlStore.Cfg, + pCtxProvider := plugincontext.ProvideService(cfg, localcache.ProvideService(), &pluginstore.FakePluginStore{ PluginList: []pluginstore.Plugin{ {JSONData: plugins.JSONData{ID: "postgres"}}, diff --git a/pkg/services/queryhistory/queryhistory_test.go b/pkg/services/queryhistory/queryhistory_test.go index 80535378d86..2b6a1cee6ef 100644 --- a/pkg/services/queryhistory/queryhistory_test.go +++ b/pkg/services/queryhistory/queryhistory_test.go @@ -55,7 +55,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo Form: url.Values{}, }} ctx.Req.Header.Add("Content-Type", "application/json") - sqlStore := db.InitTestDB(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) service := QueryHistoryService{ Cfg: setting.NewCfg(), store: sqlStore, @@ -63,9 +63,9 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo } service.Cfg.QueryHistoryEnabled = true quotaService := quotatest.New(false, nil) - orgSvc, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService) + orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sqlStore, orgSvc, sqlStore.Cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) require.NoError(t, err) usr := user.SignedInUser{ diff --git a/pkg/services/quota/quotaimpl/quota_test.go b/pkg/services/quota/quotaimpl/quota_test.go index f83418a3b71..3d127cb6d58 100644 --- a/pkg/services/quota/quotaimpl/quota_test.go +++ b/pkg/services/quota/quotaimpl/quota_test.go @@ -39,7 +39,6 @@ import ( "github.com/grafana/grafana/pkg/services/secrets/fakes" secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore" secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager" - "github.com/grafana/grafana/pkg/services/sqlstore" storesrv "github.com/grafana/grafana/pkg/services/store" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" @@ -65,8 +64,8 @@ func TestIntegrationQuotaCommandsAndQueries(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - sqlStore := sqlstore.InitTestDB(t) - sqlStore.Cfg.Quota = setting.QuotaSettings{ + sqlStore, cfg := db.InitTestDBWithCfg(t) + cfg.Quota = setting.QuotaSettings{ Enabled: true, Org: setting.OrgQuota{ @@ -92,12 +91,12 @@ func TestIntegrationQuotaCommandsAndQueries(t *testing.T) { } b := bus.ProvideBus(tracing.InitializeTracerForTest()) - quotaService := ProvideService(sqlStore, sqlStore.Cfg) - orgService, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService) + quotaService := ProvideService(sqlStore, cfg) + orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - userService, err := userimpl.ProvideService(sqlStore, orgService, sqlStore.Cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + userService, err := userimpl.ProvideService(sqlStore, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) require.NoError(t, err) - setupEnv(t, sqlStore, sqlStore.Cfg, b, quotaService) + setupEnv(t, sqlStore, cfg, b, quotaService) u, err := userService.Create(context.Background(), &user.CreateUserCommand{ Name: "TestUser", @@ -126,28 +125,28 @@ func TestIntegrationQuotaCommandsAndQueries(t *testing.T) { } tag, err := quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgQuotaTarget), scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Global.Org, defaultGlobalLimits[tag]) + require.Equal(t, cfg.Quota.Global.Org, defaultGlobalLimits[tag]) tag, err = quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Global.User, defaultGlobalLimits[tag]) + require.Equal(t, cfg.Quota.Global.User, defaultGlobalLimits[tag]) tag, err = quota.NewTag(dashboards.QuotaTargetSrv, dashboards.QuotaTarget, scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Global.Dashboard, defaultGlobalLimits[tag]) + require.Equal(t, cfg.Quota.Global.Dashboard, defaultGlobalLimits[tag]) tag, err = quota.NewTag(datasources.QuotaTargetSrv, datasources.QuotaTarget, scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Global.DataSource, defaultGlobalLimits[tag]) + require.Equal(t, cfg.Quota.Global.DataSource, defaultGlobalLimits[tag]) tag, err = quota.NewTag(apikey.QuotaTargetSrv, apikey.QuotaTarget, scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Global.ApiKey, defaultGlobalLimits[tag]) + require.Equal(t, cfg.Quota.Global.ApiKey, defaultGlobalLimits[tag]) tag, err = quota.NewTag(auth.QuotaTargetSrv, auth.QuotaTarget, scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Global.Session, defaultGlobalLimits[tag]) + require.Equal(t, cfg.Quota.Global.Session, defaultGlobalLimits[tag]) tag, err = quota.NewTag(ngalertmodels.QuotaTargetSrv, ngalertmodels.QuotaTarget, scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Global.AlertRule, defaultGlobalLimits[tag]) + require.Equal(t, cfg.Quota.Global.AlertRule, defaultGlobalLimits[tag]) tag, err = quota.NewTag(storesrv.QuotaTargetSrv, storesrv.QuotaTarget, scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Global.File, defaultGlobalLimits[tag]) + require.Equal(t, cfg.Quota.Global.File, defaultGlobalLimits[tag]) // fetch default limit/usage for org defaultOrgLimits := make(map[quota.Tag]int64) @@ -163,19 +162,19 @@ func TestIntegrationQuotaCommandsAndQueries(t *testing.T) { } tag, err = quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Org.User, defaultOrgLimits[tag]) + require.Equal(t, cfg.Quota.Org.User, defaultOrgLimits[tag]) tag, err = quota.NewTag(dashboards.QuotaTargetSrv, dashboards.QuotaTarget, scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Org.Dashboard, defaultOrgLimits[tag]) + require.Equal(t, cfg.Quota.Org.Dashboard, defaultOrgLimits[tag]) tag, err = quota.NewTag(datasources.QuotaTargetSrv, datasources.QuotaTarget, scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Org.DataSource, defaultOrgLimits[tag]) + require.Equal(t, cfg.Quota.Org.DataSource, defaultOrgLimits[tag]) tag, err = quota.NewTag(apikey.QuotaTargetSrv, apikey.QuotaTarget, scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Org.ApiKey, defaultOrgLimits[tag]) + require.Equal(t, cfg.Quota.Org.ApiKey, defaultOrgLimits[tag]) tag, err = quota.NewTag(ngalertmodels.QuotaTargetSrv, ngalertmodels.QuotaTarget, scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.Org.AlertRule, defaultOrgLimits[tag]) + require.Equal(t, cfg.Quota.Org.AlertRule, defaultOrgLimits[tag]) // fetch default limit/usage for user defaultUserLimits := make(map[quota.Tag]int64) @@ -191,7 +190,7 @@ func TestIntegrationQuotaCommandsAndQueries(t *testing.T) { } tag, err = quota.NewTag(quota.TargetSrv(org.QuotaTargetSrv), quota.Target(org.OrgUserQuotaTarget), scope) require.NoError(t, err) - require.Equal(t, sqlStore.Cfg.Quota.User.Org, defaultUserLimits[tag]) + require.Equal(t, cfg.Quota.User.Org, defaultUserLimits[tag]) t.Run("Given saved org quota for users", func(t *testing.T) { // update quota for the created org and limit users to 1 @@ -225,10 +224,13 @@ func TestIntegrationQuotaCommandsAndQueries(t *testing.T) { t.Run("Should be able to get zero used org alert quota when table does not exist (ngalert is not enabled - default case)", func(t *testing.T) { // disable Grafana Alerting - cfg := *sqlStore.Cfg + alertingCfg := cfg.UnifiedAlerting + defer func() { + cfg.UnifiedAlerting = alertingCfg + }() cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{Enabled: util.Pointer(false)} - quotaSrv := ProvideService(sqlStore, &cfg) + quotaSrv := ProvideService(sqlStore, cfg) q, err := getQuotaBySrvTargetScope(t, quotaSrv, ngalertmodels.QuotaTargetSrv, ngalertmodels.QuotaTarget, quota.OrgScope, "a.ScopeParameters{OrgID: o.ID}) require.NoError(t, err) diff --git a/pkg/services/serviceaccounts/database/store_test.go b/pkg/services/serviceaccounts/database/store_test.go index 1f60db4d4f4..dfd9b688edb 100644 --- a/pkg/services/serviceaccounts/database/store_test.go +++ b/pkg/services/serviceaccounts/database/store_test.go @@ -221,8 +221,7 @@ func TestStore_DeleteServiceAccount(t *testing.T) { func setupTestDatabase(t *testing.T) (db.DB, *ServiceAccountsStoreImpl) { t.Helper() - db := db.InitTestDB(t) - cfg := db.Cfg + db, cfg := db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) apiKeyService, err := apikeyimpl.ProvideService(db, cfg, quotaService) require.NoError(t, err) diff --git a/pkg/services/sqlstore/bulk_test.go b/pkg/services/sqlstore/bulk_test.go index 1659ccd40ae..92063a1e88c 100644 --- a/pkg/services/sqlstore/bulk_test.go +++ b/pkg/services/sqlstore/bulk_test.go @@ -64,7 +64,7 @@ func TestIntegrationBulkOps(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - db := InitTestDB(t) + db, _ := InitTestDB(t) err := db.engine.Sync(new(bulkTestItem)) require.NoError(t, err) diff --git a/pkg/services/sqlstore/permissions/dashboard_test.go b/pkg/services/sqlstore/permissions/dashboard_test.go index 00a77a7964e..1f214851880 100644 --- a/pkg/services/sqlstore/permissions/dashboard_test.go +++ b/pkg/services/sqlstore/permissions/dashboard_test.go @@ -697,13 +697,13 @@ func setupTest(t *testing.T, numFolders, numDashboards int, permissions []access func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol.Permission, orgID int64, features featuremgmt.FeatureToggles) db.DB { t.Helper() - db := sqlstore.InitTestDB(t) + db, cfg := db.InitTestDBWithCfg(t) // dashboard store commands that should be called. - dashStore, err := database.ProvideDashboardStore(db, db.Cfg, features, tagimpl.ProvideService(db), quotatest.New(false, nil)) + dashStore, err := database.ProvideDashboardStore(db, cfg, features, tagimpl.ProvideService(db), quotatest.New(false, nil)) require.NoError(t, err) - folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), db.Cfg, dashStore, folderimpl.ProvideDashboardFolderStore(db), db, features, supportbundlestest.NewFakeBundleService(), nil) + folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashStore, folderimpl.ProvideDashboardFolderStore(db), db, features, supportbundlestest.NewFakeBundleService(), nil) // create parent folder parent, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ diff --git a/pkg/services/sqlstore/permissions/dashboards_bench_test.go b/pkg/services/sqlstore/permissions/dashboards_bench_test.go index ca804da2462..7ced246ba09 100644 --- a/pkg/services/sqlstore/permissions/dashboards_bench_test.go +++ b/pkg/services/sqlstore/permissions/dashboards_bench_test.go @@ -77,14 +77,14 @@ func setupBenchMark(b *testing.B, usr user.SignedInUser, features featuremgmt.Fe nestingLevel = folder.MaxNestedFolderDepth } - store := db.InitTestDB(b) + store, cfg := db.InitTestDBWithCfg(b) quotaService := quotatest.New(false, nil) - dashboardWriteStore, err := database.ProvideDashboardStore(store, store.Cfg, features, tagimpl.ProvideService(store), quotaService) + dashboardWriteStore, err := database.ProvideDashboardStore(store, cfg, features, tagimpl.ProvideService(store), quotaService) require.NoError(b, err) - folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), store.Cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store), store, features, supportbundlestest.NewFakeBundleService(), nil) + folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store), store, features, supportbundlestest.NewFakeBundleService(), nil) origNewGuardian := guardian.New guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true}) diff --git a/pkg/services/sqlstore/session_test.go b/pkg/services/sqlstore/session_test.go index df6a9f66d62..5021bb0230c 100644 --- a/pkg/services/sqlstore/session_test.go +++ b/pkg/services/sqlstore/session_test.go @@ -12,7 +12,7 @@ import ( ) func TestRetryingDisabled(t *testing.T) { - store := InitTestDB(t) + store, _ := InitTestDB(t) require.Equal(t, 0, store.dbCfg.QueryRetries) funcToTest := map[string]func(ctx context.Context, callback DBTransactionFunc) error{ @@ -63,7 +63,7 @@ func TestRetryingDisabled(t *testing.T) { } func TestRetryingOnFailures(t *testing.T) { - store := InitTestDB(t) + store, _ := InitTestDB(t) store.dbCfg.QueryRetries = 5 funcToTest := map[string]func(ctx context.Context, callback DBTransactionFunc) error{ diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 59faffe6db4..4b17a54e37d 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -37,7 +37,7 @@ import ( type ContextSessionKey struct{} type SQLStore struct { - Cfg *setting.Cfg + cfg *setting.Cfg features featuremgmt.FeatureToggles sqlxsession *session.SessionDB @@ -45,7 +45,7 @@ type SQLStore struct { dbCfg *DatabaseConfig engine *xorm.Engine log log.Logger - Dialect migrator.Dialect + dialect migrator.Dialect skipEnsureDefaultOrgAndUser bool migrations registry.DatabaseMigrator tracer tracing.Tracer @@ -114,7 +114,7 @@ func NewSQLStoreWithoutSideEffects(cfg *setting.Cfg, func newSQLStore(cfg *setting.Cfg, engine *xorm.Engine, migrations registry.DatabaseMigrator, bus bus.Bus, tracer tracing.Tracer, opts ...InitTestDBOpt) (*SQLStore, error) { ss := &SQLStore{ - Cfg: cfg, + cfg: cfg, log: log.New("sqlstore"), skipEnsureDefaultOrgAndUser: false, migrations: migrations, @@ -131,7 +131,7 @@ func newSQLStore(cfg *setting.Cfg, engine *xorm.Engine, return nil, fmt.Errorf("%v: %w", "failed to connect to database", err) } - ss.Dialect = migrator.NewDialect(ss.engine.DriverName()) + ss.dialect = migrator.NewDialect(ss.engine.DriverName()) // if err := ss.Reset(); err != nil { // return nil, err @@ -155,7 +155,7 @@ func (ss *SQLStore) Migrate(isDatabaseLockingEnabled bool) error { return nil } - migrator := migrator.NewMigrator(ss.engine, ss.Cfg) + migrator := migrator.NewMigrator(ss.engine, ss.cfg) ss.migrations.AddMigration(migrator) return migrator.Start(isDatabaseLockingEnabled, ss.dbCfg.MigrationLockAttemptTimeout) @@ -178,7 +178,7 @@ func (ss *SQLStore) Quote(value string) string { // GetDialect return the dialect func (ss *SQLStore) GetDialect() migrator.Dialect { - return ss.Dialect + return ss.dialect } func (ss *SQLStore) GetDBType() core.DbType { @@ -210,7 +210,7 @@ func (ss *SQLStore) ensureMainOrgAndAdminUser(test bool) error { var stats stats.SystemUserCountStats // TODO: Should be able to rename "Count" to "count", for more standard SQL style // Just have to make sure it gets deserialized properly into models.SystemUserCountStats - rawSQL := `SELECT COUNT(id) AS Count FROM ` + ss.Dialect.Quote("user") + rawSQL := `SELECT COUNT(id) AS Count FROM ` + ss.dialect.Quote("user") if _, err := sess.SQL(rawSQL).Get(&stats); err != nil { return fmt.Errorf("could not determine if admin user exists: %w", err) } @@ -220,19 +220,19 @@ func (ss *SQLStore) ensureMainOrgAndAdminUser(test bool) error { } // ensure admin user - if !ss.Cfg.DisableInitAdminCreation { + if !ss.cfg.DisableInitAdminCreation { ss.log.Debug("Creating default admin user") if _, err := ss.createUser(ctx, sess, user.CreateUserCommand{ - Login: ss.Cfg.AdminUser, - Email: ss.Cfg.AdminEmail, - Password: user.Password(ss.Cfg.AdminPassword), + Login: ss.cfg.AdminUser, + Email: ss.cfg.AdminEmail, + Password: user.Password(ss.cfg.AdminPassword), IsAdmin: true, }); err != nil { return fmt.Errorf("failed to create admin user: %s", err) } - ss.log.Info("Created default admin", "user", ss.Cfg.AdminUser) + ss.log.Info("Created default admin", "user", ss.cfg.AdminUser) } ss.log.Debug("Creating default org", "name", mainOrgName) @@ -254,14 +254,14 @@ func (ss *SQLStore) initEngine(engine *xorm.Engine) error { return nil } - dbCfg, err := NewDatabaseConfig(ss.Cfg, ss.features) + dbCfg, err := NewDatabaseConfig(ss.cfg, ss.features) if err != nil { return err } ss.dbCfg = dbCfg - if ss.Cfg.DatabaseInstrumentQueries { + if ss.cfg.DatabaseInstrumentQueries { ss.dbCfg.Type = WrapDatabaseDriverWithHooks(ss.dbCfg.Type, ss.tracer) } @@ -316,7 +316,7 @@ func (ss *SQLStore) initEngine(engine *xorm.Engine) error { engine.SetConnMaxLifetime(time.Second * time.Duration(ss.dbCfg.ConnMaxLifetime)) // configure sql logging - debugSQL := ss.Cfg.Raw.Section("database").Key("log_queries").MustBool(false) + debugSQL := ss.cfg.Raw.Section("database").Key("log_queries").MustBool(false) if !debugSQL { engine.SetLogger(&xorm.DiscardLogger{}) } else { @@ -425,7 +425,7 @@ func InitTestDBWithMigration(t sqlutil.ITestDB, migration registry.DatabaseMigra } // InitTestDB initializes the test DB. -func InitTestDB(t sqlutil.ITestDB, opts ...InitTestDBOpt) *SQLStore { +func InitTestDB(t sqlutil.ITestDB, opts ...InitTestDBOpt) (*SQLStore, *setting.Cfg) { t.Helper() features := getFeaturesForTesting(opts...) cfg := getCfgForTesting(opts...) @@ -434,7 +434,7 @@ func InitTestDB(t sqlutil.ITestDB, opts ...InitTestDBOpt) *SQLStore { if err != nil { t.Fatalf("failed to initialize sql store: %s", err) } - return store + return store, store.cfg } func SetupTestDB() { @@ -605,9 +605,9 @@ func TestMain(m *testing.M) { } // nolint:staticcheck - testSQLStore.Cfg.IsFeatureToggleEnabled = features.IsEnabledGlobally + testSQLStore.cfg.IsFeatureToggleEnabled = features.IsEnabledGlobally - if err := testSQLStore.Dialect.TruncateDBTables(testSQLStore.GetEngine()); err != nil { + if err := testSQLStore.dialect.TruncateDBTables(testSQLStore.GetEngine()); err != nil { return nil, err } if err := testSQLStore.Reset(); err != nil { diff --git a/pkg/services/sqlstore/sqlstore_test.go b/pkg/services/sqlstore/sqlstore_test.go index 54da359d9f0..7c62a7a8c04 100644 --- a/pkg/services/sqlstore/sqlstore_test.go +++ b/pkg/services/sqlstore/sqlstore_test.go @@ -20,7 +20,7 @@ func TestMain(m *testing.M) { } func TestIntegrationIsUniqueConstraintViolation(t *testing.T) { - store := InitTestDB(t) + store, _ := InitTestDB(t) testCases := []struct { desc string @@ -32,12 +32,12 @@ func TestIntegrationIsUniqueConstraintViolation(t *testing.T) { // Attempt to insert org with provided ID (primary key) twice now := time.Now() org := org.Org{Name: "test org primary key violation", Created: now, Updated: now, ID: 42} - err := sess.InsertId(&org, store.Dialect) + err := sess.InsertId(&org, store.dialect) require.NoError(t, err) // Provide a different name to avoid unique constraint violation org.Name = "test org 2" - return sess.InsertId(&org, store.Dialect) + return sess.InsertId(&org, store.dialect) }, }, { @@ -46,12 +46,12 @@ func TestIntegrationIsUniqueConstraintViolation(t *testing.T) { // Attempt to insert org with reserved name now := time.Now() org := org.Org{Name: "test org unique constrain violation", Created: now, Updated: now, ID: 43} - err := sess.InsertId(&org, store.Dialect) + err := sess.InsertId(&org, store.dialect) require.NoError(t, err) // Provide a different ID to avoid primary key violation org.ID = 44 - return sess.InsertId(&org, store.Dialect) + return sess.InsertId(&org, store.dialect) }, }, } @@ -62,7 +62,7 @@ func TestIntegrationIsUniqueConstraintViolation(t *testing.T) { return tc.f(t, sess) }) require.Error(t, err) - assert.True(t, store.Dialect.IsUniqueConstraintViolation(err)) + assert.True(t, store.dialect.IsUniqueConstraintViolation(err)) }) } } diff --git a/pkg/services/sqlstore/transactions_test.go b/pkg/services/sqlstore/transactions_test.go index 508b0bb71db..bbf3ad3f85c 100644 --- a/pkg/services/sqlstore/transactions_test.go +++ b/pkg/services/sqlstore/transactions_test.go @@ -13,7 +13,7 @@ func TestIntegrationReuseSessionWithTransaction(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - ss := InitTestDB(t) + ss, _ := InitTestDB(t) t.Run("top level transaction", func(t *testing.T) { var outerSession *DBSession @@ -73,7 +73,7 @@ func TestIntegrationPublishAfterCommitWithNestedTransactions(t *testing.T) { t.Skip("skipping integration test") } - ss := InitTestDB(t) + ss, _ := InitTestDB(t) ctx := context.Background() // On X success diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 1b2a9249bd3..00861d9bc39 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -16,7 +16,7 @@ import ( const mainOrgName = "Main Org." func (ss *SQLStore) getOrgIDForNewUser(sess *DBSession, args user.CreateUserCommand) (int64, error) { - if ss.Cfg.AutoAssignOrg && args.OrgID != 0 { + if ss.cfg.AutoAssignOrg && args.OrgID != 0 { if err := verifyExistingOrg(sess, args.OrgID); err != nil { return -1, err } @@ -114,11 +114,11 @@ func (ss *SQLStore) createUser(ctx context.Context, sess *DBSession, args user.C Updated: time.Now(), } - if ss.Cfg.AutoAssignOrg && !usr.IsAdmin { + if ss.cfg.AutoAssignOrg && !usr.IsAdmin { if len(args.DefaultOrgRole) > 0 { orgUser.Role = org.RoleType(args.DefaultOrgRole) } else { - orgUser.Role = org.RoleType(ss.Cfg.AutoAssignOrgRole) + orgUser.Role = org.RoleType(ss.cfg.AutoAssignOrgRole) } } @@ -144,8 +144,8 @@ func verifyExistingOrg(sess *DBSession, orgId int64) error { func (ss *SQLStore) getOrCreateOrg(sess *DBSession, orgName string) (int64, error) { var org org.Org - if ss.Cfg.AutoAssignOrg { - has, err := sess.Where("id=?", ss.Cfg.AutoAssignOrgId).Get(&org) + if ss.cfg.AutoAssignOrg { + has, err := sess.Where("id=?", ss.cfg.AutoAssignOrgId).Get(&org) if err != nil { return 0, err } @@ -154,18 +154,18 @@ func (ss *SQLStore) getOrCreateOrg(sess *DBSession, orgName string) (int64, erro } ss.log.Debug("auto assigned organization not found") - if ss.Cfg.AutoAssignOrgId != 1 { + if ss.cfg.AutoAssignOrgId != 1 { ss.log.Error("Could not create user: organization ID does not exist", "orgID", - ss.Cfg.AutoAssignOrgId) + ss.cfg.AutoAssignOrgId) return 0, fmt.Errorf("could not create user: organization ID %d does not exist", - ss.Cfg.AutoAssignOrgId) + ss.cfg.AutoAssignOrgId) } org.Name = mainOrgName org.Created = time.Now() org.Updated = org.Created - org.ID = int64(ss.Cfg.AutoAssignOrgId) - if err := sess.InsertId(&org, ss.Dialect); err != nil { + org.ID = int64(ss.cfg.AutoAssignOrgId) + if err := sess.InsertId(&org, ss.dialect); err != nil { ss.log.Error("failed to insert organization with provided id", "org_id", org.ID, "err", err) // ignore failure if for some reason the organization exists if ss.GetDialect().IsUniqueConstraintViolation(err) { diff --git a/pkg/services/sqlstore/user_test.go b/pkg/services/sqlstore/user_test.go index 03131de4665..eb2e4fb7c9a 100644 --- a/pkg/services/sqlstore/user_test.go +++ b/pkg/services/sqlstore/user_test.go @@ -14,13 +14,13 @@ func TestIntegrationGetOrCreateOrg(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test in short mode") } - ss := InitTestDB(t) + ss, _ := InitTestDB(t) err := ss.WithNewDbSession(context.Background(), func(sess *DBSession) error { // Create the org only: - ss.Cfg.AutoAssignOrg = true - ss.Cfg.DisableInitAdminCreation = true - ss.Cfg.AutoAssignOrgId = 1 + ss.cfg.AutoAssignOrg = true + ss.cfg.DisableInitAdminCreation = true + ss.cfg.AutoAssignOrgId = 1 createdOrgID, err := ss.getOrCreateOrg(sess, mainOrgName) require.NoError(t, err) require.Equal(t, int64(1), createdOrgID) diff --git a/pkg/services/stats/statsimpl/stats_test.go b/pkg/services/stats/statsimpl/stats_test.go index 36565ee1835..f686c6a4251 100644 --- a/pkg/services/stats/statsimpl/stats_test.go +++ b/pkg/services/stats/statsimpl/stats_test.go @@ -16,7 +16,6 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/quota/quotatest" - "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/stats" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/user" @@ -33,9 +32,9 @@ func TestIntegrationStatsDataAccess(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - db := sqlstore.InitTestDB(t) + db, cfg := db.InitTestDBWithCfg(t) statsService := &sqlStatsService{db: db} - populateDB(t, db, db.Cfg) + populateDB(t, db, cfg) t.Run("Get system stats should not results in error", func(t *testing.T) { query := stats.GetSystemStatsQuery{} @@ -50,7 +49,7 @@ func TestIntegrationStatsDataAccess(t *testing.T) { assert.Equal(t, int64(0), result.APIKeys) assert.Equal(t, int64(2), result.Correlations) assert.NotNil(t, result.DatabaseCreatedTime) - assert.Equal(t, db.Dialect.DriverName(), result.DatabaseDriver) + assert.Equal(t, db.GetDialect().DriverName(), result.DatabaseDriver) }) t.Run("Get system user count stats should not results in error", func(t *testing.T) { @@ -155,8 +154,8 @@ func TestIntegration_GetAdminStats(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - db := sqlstore.InitTestDB(t) - statsService := ProvideService(&setting.Cfg{}, db) + db, cfg := db.InitTestDBWithCfg(t) + statsService := ProvideService(cfg, db) query := stats.GetAdminStatsQuery{} _, err := statsService.GetAdminStats(context.Background(), &query) diff --git a/pkg/services/store/entity/sqlstash/sql_storage_server_test.go b/pkg/services/store/entity/sqlstash/sql_storage_server_test.go index 00353826fad..bd4558e553e 100644 --- a/pkg/services/store/entity/sqlstash/sql_storage_server_test.go +++ b/pkg/services/store/entity/sqlstash/sql_storage_server_test.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/store/entity" "github.com/grafana/grafana/pkg/services/store/entity/db/dbimpl" - "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tests/testsuite" ) @@ -125,15 +124,15 @@ func TestCreate(t *testing.T) { } func setUpTestServer(t *testing.T) entity.EntityStoreServer { - sqlStore := db.InitTestDB(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) entityDB, err := dbimpl.ProvideEntityDB( sqlStore, - setting.NewCfg(), + cfg, featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorage)) require.NoError(t, err) - traceConfig, err := tracing.ParseTracingConfig(sqlStore.Cfg) + traceConfig, err := tracing.ParseTracingConfig(cfg) require.NoError(t, err) tracer, err := tracing.ProvideService(traceConfig) require.NoError(t, err) diff --git a/pkg/services/team/teamimpl/store_test.go b/pkg/services/team/teamimpl/store_test.go index d23ab695b02..4da956f05d6 100644 --- a/pkg/services/team/teamimpl/store_test.go +++ b/pkg/services/team/teamimpl/store_test.go @@ -34,8 +34,8 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { t.Skip("skipping integration test") } t.Run("Testing Team commands and queries", func(t *testing.T) { - sqlStore := db.InitTestDB(t) - teamSvc, err := ProvideService(sqlStore, sqlStore.Cfg) + sqlStore, cfg := db.InitTestDBWithCfg(t) + teamSvc, err := ProvideService(sqlStore, cfg) require.NoError(t, err) testUser := &user.SignedInUser{ OrgID: 1, @@ -47,10 +47,10 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { }, }, } - quotaService := quotaimpl.ProvideService(sqlStore, sqlStore.Cfg) - orgSvc, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService) + quotaService := quotaimpl.ProvideService(sqlStore, cfg) + orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, sqlStore.Cfg, teamSvc, nil, quotaService, + userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, teamSvc, nil, quotaService, supportbundlestest.NewFakeBundleService()) require.NoError(t, err) @@ -401,10 +401,10 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { t.Run("Should be able to exclude service accounts from teamembers", func(t *testing.T) { sqlStore = db.InitTestDB(t) - quotaService := quotaimpl.ProvideService(sqlStore, sqlStore.Cfg) - orgSvc, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService) + quotaService := quotaimpl.ProvideService(sqlStore, cfg) + orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, sqlStore.Cfg, teamSvc, nil, quotaService, supportbundlestest.NewFakeBundleService()) + userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, teamSvc, nil, quotaService, supportbundlestest.NewFakeBundleService()) require.NoError(t, err) setup() userCmd = user.CreateUserCommand{ @@ -489,8 +489,8 @@ func TestIntegrationSQLStore_SearchTeams(t *testing.T) { }, } - store := db.InitTestDB(t, db.InitTestDBOpt{}) - teamSvc, err := ProvideService(store, store.Cfg) + store, cfg := db.InitTestDBWithCfg(t, db.InitTestDBOpt{}) + teamSvc, err := ProvideService(store, cfg) require.NoError(t, err) // Seed 10 teams @@ -560,9 +560,9 @@ func TestIntegrationSQLStore_GetTeamMembers_ACFilter(t *testing.T) { require.NoError(t, errAddMember) } - store := db.InitTestDB(t, db.InitTestDBOpt{}) - setup(store, store.Cfg) - teamSvc, err := ProvideService(store, store.Cfg) + store, cfg := db.InitTestDBWithCfg(t, db.InitTestDBOpt{}) + setup(store, cfg) + teamSvc, err := ProvideService(store, cfg) require.NoError(t, err) type getTeamMembersTestCase struct { diff --git a/pkg/services/temp_user/tempuserimpl/store_test.go b/pkg/services/temp_user/tempuserimpl/store_test.go index 491850afe51..811dae0cd44 100644 --- a/pkg/services/temp_user/tempuserimpl/store_test.go +++ b/pkg/services/temp_user/tempuserimpl/store_test.go @@ -31,8 +31,8 @@ func TestIntegrationTempUserCommandsAndQueries(t *testing.T) { Status: tempuser.TmpUserInvitePending, } setup := func(t *testing.T) { - db := db.InitTestDB(t) - store = &xormStore{db: db, cfg: db.Cfg} + db, cfg := db.InitTestDBWithCfg(t) + store = &xormStore{db: db, cfg: cfg} tempUser, err = store.CreateTempUser(context.Background(), &cmd) require.Nil(t, err) } @@ -112,8 +112,8 @@ func TestIntegrationTempUserCommandsAndQueries(t *testing.T) { Status: tempuser.TmpUserEmailUpdateStarted, InvitedByUserID: userID, } - db := db.InitTestDB(t) - store = &xormStore{db: db, cfg: db.Cfg} + db, cfg := db.InitTestDBWithCfg(t) + store = &xormStore{db: db, cfg: cfg} for i := 0; i < verifications; i++ { tempUser, err = store.CreateTempUser(context.Background(), &cmd) @@ -152,8 +152,8 @@ func TestIntegrationTempUserCommandsAndQueries(t *testing.T) { Status: tempuser.TmpUserEmailUpdateStarted, InvitedByUserID: 99, } - db := db.InitTestDB(t) - store = &xormStore{db: db, cfg: db.Cfg} + db, cfg := db.InitTestDBWithCfg(t) + store = &xormStore{db: db, cfg: cfg} tempUser, err = store.CreateTempUser(context.Background(), &cmd) require.Nil(t, err) diff --git a/pkg/services/user/userimpl/store_test.go b/pkg/services/user/userimpl/store_test.go index cf559938a87..6803ff4ce01 100644 --- a/pkg/services/user/userimpl/store_test.go +++ b/pkg/services/user/userimpl/store_test.go @@ -64,8 +64,7 @@ func TestIntegrationUserGet(t *testing.T) { t.Skip("skipping integration test") } - ss := db.InitTestDB(t) - cfg := ss.Cfg + ss, cfg := db.InitTestDBWithCfg(t) userStore := ProvideStore(ss, cfg) _, errUser := userStore.Insert(context.Background(), @@ -108,12 +107,12 @@ func TestIntegrationUserDataAccess(t *testing.T) { t.Skip("skipping integration test") } - ss := db.InitTestDB(t) - quotaService := quotaimpl.ProvideService(ss, ss.Cfg) - orgService, err := orgimpl.ProvideService(ss, ss.Cfg, quotaService) + ss, cfg := db.InitTestDBWithCfg(t) + quotaService := quotaimpl.ProvideService(ss, cfg) + orgService, err := orgimpl.ProvideService(ss, cfg, quotaService) require.NoError(t, err) userStore := ProvideStore(ss, setting.NewCfg()) - usrSvc, err := ProvideService(ss, orgService, ss.Cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := ProvideService(ss, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) require.NoError(t, err) usr := &user.SignedInUser{ OrgID: 1, @@ -183,7 +182,7 @@ func TestIntegrationUserDataAccess(t *testing.T) { t.Run("Testing DB - creates and loads user", func(t *testing.T) { ss := db.InitTestDB(t) - _, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg) + _, usrSvc := createOrgAndUserSvc(t, ss, cfg) cmd := user.CreateUserCommand{ Email: "usertest@test.com", @@ -460,7 +459,7 @@ func TestIntegrationUserDataAccess(t *testing.T) { t.Run("get signed in user", func(t *testing.T) { ss := db.InitTestDB(t) - orgService, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg) + orgService, usrSvc := createOrgAndUserSvc(t, ss, cfg) users := createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand { return &user.CreateUserCommand{ Email: fmt.Sprint("user", i, "@test.com"), @@ -501,7 +500,7 @@ func TestIntegrationUserDataAccess(t *testing.T) { t.Run("Testing DB - grafana admin users", func(t *testing.T) { ss := db.InitTestDB(t) - _, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg) + _, usrSvc := createOrgAndUserSvc(t, ss, cfg) usr, err := usrSvc.Create(context.Background(), &user.CreateUserCommand{ Email: "admin@test.com", Name: "admin", @@ -557,8 +556,8 @@ func TestIntegrationUserDataAccess(t *testing.T) { t.Run("Testing DB - return list users based on their is_disabled flag", func(t *testing.T) { ss = db.InitTestDB(t) - _, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg) - userStore := ProvideStore(ss, ss.Cfg) + _, usrSvc := createOrgAndUserSvc(t, ss, cfg) + userStore := ProvideStore(ss, cfg) createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand { return &user.CreateUserCommand{ @@ -591,7 +590,7 @@ func TestIntegrationUserDataAccess(t *testing.T) { // Re-init DB ss := db.InitTestDB(t) - orgService, usrSvc = createOrgAndUserSvc(t, ss, ss.Cfg) + orgService, usrSvc = createOrgAndUserSvc(t, ss, cfg) users := createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand { return &user.CreateUserCommand{ @@ -615,7 +614,7 @@ func TestIntegrationUserDataAccess(t *testing.T) { // A user is an org member and has been assigned permissions // Re-init DB ss = db.InitTestDB(t) - orgService, usrSvc = createOrgAndUserSvc(t, ss, ss.Cfg) + orgService, usrSvc = createOrgAndUserSvc(t, ss, cfg) users = createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand { return &user.CreateUserCommand{ Email: fmt.Sprint("user", i, "@test.com"), @@ -657,9 +656,9 @@ func TestIntegrationUserDataAccess(t *testing.T) { t.Run("Testing DB - return list of users that the SignedInUser has permission to read", func(t *testing.T) { ss := db.InitTestDB(t) - orgService, err := orgimpl.ProvideService(ss, ss.Cfg, quotaService) + orgService, err := orgimpl.ProvideService(ss, cfg, quotaService) require.NoError(t, err) - usrSvc, err := ProvideService(ss, orgService, ss.Cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := ProvideService(ss, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) require.NoError(t, err) createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand { @@ -934,9 +933,9 @@ func TestIntegrationUserUpdate(t *testing.T) { t.Skip("skipping integration test") } - ss := db.InitTestDB(t) - userStore := ProvideStore(ss, setting.NewCfg()) - _, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg) + ss, cfg := db.InitTestDBWithCfg(t) + userStore := ProvideStore(ss, cfg) + _, usrSvc := createOrgAndUserSvc(t, ss, cfg) users := createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand { return &user.CreateUserCommand{ @@ -999,13 +998,13 @@ func createFiveTestUsers(t *testing.T, svc user.Service, fn func(i int) *user.Cr } func TestMetricsUsage(t *testing.T) { - ss := db.InitTestDB(t) + ss, cfg := db.InitTestDBWithCfg(t) userStore := ProvideStore(ss, setting.NewCfg()) - quotaService := quotaimpl.ProvideService(ss, ss.Cfg) - orgService, err := orgimpl.ProvideService(ss, ss.Cfg, quotaService) + quotaService := quotaimpl.ProvideService(ss, cfg) + orgService, err := orgimpl.ProvideService(ss, cfg, quotaService) require.NoError(t, err) - _, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg) + _, usrSvc := createOrgAndUserSvc(t, ss, cfg) t.Run("Get empty role metrics for an org", func(t *testing.T) { orgId := int64(1) diff --git a/pkg/tsdb/legacydata/service/service_test.go b/pkg/tsdb/legacydata/service/service_test.go index a38e7b43bb1..df7e0965c69 100644 --- a/pkg/tsdb/legacydata/service/service_test.go +++ b/pkg/tsdb/legacydata/service/service_test.go @@ -42,16 +42,16 @@ func TestHandleRequest(t *testing.T) { actualReq = req return backend.NewQueryDataResponse(), nil } - sqlStore := db.InitTestDB(t) + sqlStore, cfg := db.InitTestDBWithCfg(t) secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore()) secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger")) datasourcePermissions := acmock.NewMockedPermissionsService() quotaService := quotatest.New(false, nil) dsCache := datasourceservice.ProvideCacheService(localcache.ProvideService(), sqlStore, guardian.ProvideGuardian()) - dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, sqlStore.Cfg, featuremgmt.WithFeatures(), acmock.New(), datasourcePermissions, quotaService, &pluginstore.FakePluginStore{}) + dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), datasourcePermissions, quotaService, &pluginstore.FakePluginStore{}) require.NoError(t, err) - pCtxProvider := plugincontext.ProvideService(sqlStore.Cfg, localcache.ProvideService(), &pluginstore.FakePluginStore{ + pCtxProvider := plugincontext.ProvideService(cfg, localcache.ProvideService(), &pluginstore.FakePluginStore{ PluginList: []pluginstore.Plugin{{JSONData: plugins.JSONData{ID: "test"}}}, }, dsCache, dsService, pluginSettings.ProvideService(sqlStore, secretsService), pluginconfig.NewFakePluginRequestConfigProvider()) s := ProvideService(client, nil, dsService, pCtxProvider) From 2db56b9c85dc3184cad2a3ceeb3b5ff123850c63 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Wed, 24 Apr 2024 10:52:00 +0200 Subject: [PATCH 079/222] GrafanaUI: Add minWidth and maxWidth props to the Box component (#86607) * Add minWidth and maxWidth props to the Box component * Add height props, use theme spacing for sizing --- .../src/components/Layout/Box/Box.tsx | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/grafana-ui/src/components/Layout/Box/Box.tsx b/packages/grafana-ui/src/components/Layout/Box/Box.tsx index 3f4a36c6950..34ff3d88611 100644 --- a/packages/grafana-ui/src/components/Layout/Box/Box.tsx +++ b/packages/grafana-ui/src/components/Layout/Box/Box.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; +import { Property } from 'csstype'; import React, { ElementType, forwardRef, PropsWithChildren } from 'react'; import { GrafanaTheme2, ThemeSpacingTokens, ThemeShape, ThemeShadows } from '@grafana/data'; @@ -58,6 +59,15 @@ interface BoxProps extends FlexProps, Omit, 'c justifyContent?: ResponsiveProp; gap?: ResponsiveProp; + // Size props + minWidth?: ResponsiveProp>; + maxWidth?: ResponsiveProp>; + width?: ResponsiveProp>; + + minHeight?: ResponsiveProp>; + maxHeight?: ResponsiveProp>; + height?: ResponsiveProp>; + // Other props backgroundColor?: ResponsiveProp; display?: ResponsiveProp; @@ -98,6 +108,12 @@ export const Box = forwardRef>((props, boxShadow, element, gap, + width, + minWidth, + maxWidth, + height, + minHeight, + maxHeight, ...rest } = props; const styles = useStyles2( @@ -129,7 +145,13 @@ export const Box = forwardRef>((props, justifyContent, alignItems, boxShadow, - gap + gap, + width, + minWidth, + maxWidth, + height, + minHeight, + maxHeight ); const Element = element ?? 'div'; @@ -195,7 +217,13 @@ const getStyles = ( justifyContent: BoxProps['justifyContent'], alignItems: BoxProps['alignItems'], boxShadow: BoxProps['boxShadow'], - gap: BoxProps['gap'] + gap: BoxProps['gap'], + width: BoxProps['width'], + minWidth: BoxProps['minWidth'], + maxWidth: BoxProps['maxWidth'], + height: BoxProps['height'], + minHeight: BoxProps['minHeight'], + maxHeight: BoxProps['maxHeight'] ) => { return { root: css([ @@ -290,6 +318,24 @@ const getStyles = ( getResponsiveStyle(theme, gap, (val) => ({ gap: theme.spacing(val), })), + getResponsiveStyle(theme, width, (val) => ({ + width: theme.spacing(val), + })), + getResponsiveStyle(theme, minWidth, (val) => ({ + minWidth: theme.spacing(val), + })), + getResponsiveStyle(theme, maxWidth, (val) => ({ + maxWidth: theme.spacing(val), + })), + getResponsiveStyle(theme, height, (val) => ({ + height: theme.spacing(val), + })), + getResponsiveStyle(theme, minHeight, (val) => ({ + minHeight: theme.spacing(val), + })), + getResponsiveStyle(theme, maxHeight, (val) => ({ + maxHeight: theme.spacing(val), + })), ]), }; }; From d46b163810445d7369856e27a3d26f8fbc17f36a Mon Sep 17 00:00:00 2001 From: Charandas Date: Wed, 24 Apr 2024 10:40:00 +0100 Subject: [PATCH 080/222] Authn (jwt_auth): add tracing spans for validating newer use cases (#86812) --- go.work.sum | 1 + pkg/services/authn/authnimpl/service.go | 20 ++++ pkg/services/authn/authnimpl/service_test.go | 105 ++++++++++++++++++- 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/go.work.sum b/go.work.sum index aacacdeac54..001aa1abaaf 100644 --- a/go.work.sum +++ b/go.work.sum @@ -787,6 +787,7 @@ gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPj gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4= google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index bfd4698095a..880f6d2331b 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -9,6 +9,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/infra/log" @@ -123,12 +124,31 @@ func (s *Service) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id } func (s *Service) authenticate(ctx context.Context, c authn.Client, r *authn.Request) (*authn.Identity, error) { + ctx, span := s.tracer.Start(ctx, "authn.authenticate") + defer span.End() + identity, err := c.Authenticate(ctx, r) if err != nil { + span.SetStatus(codes.Error, "authenticate failed on client") + span.RecordError(err) s.errorLogFunc(ctx, err)("Failed to authenticate request", "client", c.Name(), "error", err) return nil, err } + span.SetAttributes( + attribute.String("identity.ID", identity.ID.String()), + attribute.String("identity.AuthID", identity.AuthID), + attribute.String("identity.AuthenticatedBy", identity.AuthenticatedBy), + ) + + if len(identity.ClientParams.FetchPermissionsParams.ActionsLookup) > 0 { + span.SetAttributes(attribute.StringSlice("identity.ClientParams.FetchPermissionsParams.ActionsLookup", identity.ClientParams.FetchPermissionsParams.ActionsLookup)) + } + + if len(identity.ClientParams.FetchPermissionsParams.Roles) > 0 { + span.SetAttributes(attribute.StringSlice("identity.ClientParams.FetchPermissionsParams.Roles", identity.ClientParams.FetchPermissionsParams.Roles)) + } + if err := s.runPostAuthHooks(ctx, identity, r); err != nil { s.errorLogFunc(ctx, err)("Failed to run post auth hook", "client", c.Name(), "id", identity.ID, "error", err) return nil, err diff --git a/pkg/services/authn/authnimpl/service_test.go b/pkg/services/authn/authnimpl/service_test.go index c96147eb7eb..57f05217c23 100644 --- a/pkg/services/authn/authnimpl/service_test.go +++ b/pkg/services/authn/authnimpl/service_test.go @@ -6,10 +6,14 @@ import ( "net" "net/http" "net/url" + "slices" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/codes" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -44,13 +48,54 @@ func TestService_Authenticate(t *testing.T) { }, expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, }, + { + desc: "should succeed with authentication for configured client for identity with fetch permissions params", + clients: []authn.Client{ + &authntest.FakeClient{ + ExpectedTest: true, + ExpectedIdentity: &authn.Identity{ + ID: authn.MustParseNamespaceID("user:2"), + ClientParams: authn.ClientParams{ + FetchPermissionsParams: authn.FetchPermissionsParams{ + ActionsLookup: []string{ + "datasources:read", + "datasources:query", + }, + Roles: []string{ + "fixed:datasources:reader", + }, + }, + }, + }, + }, + }, + expectedIdentity: &authn.Identity{ + ID: authn.MustParseNamespaceID("user:2"), + ClientParams: authn.ClientParams{ + FetchPermissionsParams: authn.FetchPermissionsParams{ + ActionsLookup: []string{ + "datasources:read", + "datasources:query", + }, + Roles: []string{ + "fixed:datasources:reader", + }, + }, + }, + }, + }, { desc: "should succeed with authentication for second client when first test fail", clients: []authn.Client{ &authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 1, ExpectedTest: false}, - &authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 2, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")}}, + &authntest.FakeClient{ + ExpectedName: "2", + ExpectedPriority: 2, + ExpectedTest: true, + ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"}, + }, }, - expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")}, + expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"}, }, { desc: "should succeed with authentication for third client when error happened in first", @@ -90,16 +135,72 @@ func TestService_Authenticate(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { + spanRecorder := tracetest.NewSpanRecorder() + tracer := tracing.InitializeTracerForTest(tracing.WithSpanProcessor(spanRecorder)) + svc := setupTests(t, func(svc *Service) { + svc.tracer = tracer + for _, c := range tt.clients { svc.RegisterClient(c) } }) identity, err := svc.Authenticate(context.Background(), &authn.Request{}) + spans := spanRecorder.Ended() if len(tt.expectedErrors) == 0 { assert.NoError(t, err) assert.EqualValues(t, tt.expectedIdentity, identity) + + matchedClients := make([]*authntest.FakeClient, 0) + for _, client := range tt.clients { + fakeClient, _ := client.(*authntest.FakeClient) + if fakeClient.ExpectedTest { + matchedClients = append(matchedClients, fakeClient) + } + } + require.Len(t, spans, 1+len(matchedClients), "must have spans 1+ number of clients tried") + + spansTested := make([]sdktrace.ReadOnlySpan, 0) + for _, span := range spans { + if span.Name() != "authn.Authenticate" { + spansTested = append(spansTested, span) + } + } + + assert.Len(t, spansTested, len(matchedClients), "expected spans with name authn.authenticate to match number of clients tested") + + // since this is a success case, at least one span should have all 3 attributes + passedAuthnIndex := slices.IndexFunc(spansTested, func(span sdktrace.ReadOnlySpan) bool { + return len(span.Attributes()) >= 3 // more than 3 when there are ClientParams in the identity + }) + require.NotEqual(t, -1, passedAuthnIndex, "no spans found all 3 attributes - passed case should have authn attributes set") + passedAuthnSpan := spansTested[passedAuthnIndex] + for _, attr := range passedAuthnSpan.Attributes() { + switch attr.Key { + case "identity.ID": + assert.Equal(t, tt.expectedIdentity.ID.String(), attr.Value.AsString()) + case "identity.AuthID": + assert.Equal(t, tt.expectedIdentity.AuthID, attr.Value.AsString()) + case "identity.AuthenticatedBy": + assert.Equal(t, tt.expectedIdentity.AuthenticatedBy, attr.Value.AsString()) + case "identity.ClientParams.FetchPermissionsParams.ActionsLookup": + if len(tt.expectedIdentity.ClientParams.FetchPermissionsParams.ActionsLookup) > 0 { + assert.Equal(t, tt.expectedIdentity.ClientParams.FetchPermissionsParams.ActionsLookup, attr.Value.AsStringSlice()) + } + case "identity.ClientParams.FetchPermissionsParams.Roles": + if len(tt.expectedIdentity.ClientParams.FetchPermissionsParams.Roles) > 0 { + assert.Equal(t, tt.expectedIdentity.ClientParams.FetchPermissionsParams.Roles, attr.Value.AsStringSlice()) + } + } + } + + if len(matchedClients) > 1 { + failedAuthnIndex := slices.IndexFunc(spansTested, func(span sdktrace.ReadOnlySpan) bool { + return span.Status().Code == codes.Error + }) + assert.NotEqual(t, -1, failedAuthnIndex, "no spans found for the error case - at least one client in multi client test must have failed") + } } else { for _, e := range tt.expectedErrors { assert.ErrorIs(t, err, e) From e7f40493e4ad6e76b888a239c09e59c2bb40c705 Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Wed, 24 Apr 2024 11:45:32 +0200 Subject: [PATCH 081/222] DashboardScene: Measure and report scene load time (#86267) * measure scene load time * Fix tests that fail due to performance not being the proper global performance object in jest * add isScene parameter to tracking test --- public/app/core/utils/metrics.ts | 6 +- .../pages/DashboardScenePageStateManager.ts | 7 +++ .../transformSaveModelToScene.ts | 14 ----- .../features/dashboard/state/initDashboard.ts | 9 ++- .../features/dashboard/utils/tracking.test.ts | 1 + .../app/features/dashboard/utils/tracking.ts | 62 ++++++++++++++----- 6 files changed, 65 insertions(+), 34 deletions(-) diff --git a/public/app/core/utils/metrics.ts b/public/app/core/utils/metrics.ts index 97551a721cc..e244851aa74 100644 --- a/public/app/core/utils/metrics.ts +++ b/public/app/core/utils/metrics.ts @@ -1,7 +1,7 @@ import { reportPerformance } from '../services/echo/EchoSrv'; export function startMeasure(eventName: string) { - if (!performance) { + if (!performance || !performance.mark) { return; } @@ -13,7 +13,7 @@ export function startMeasure(eventName: string) { } export function stopMeasure(eventName: string) { - if (!performance) { + if (!performance || !performance.mark) { return; } @@ -29,7 +29,9 @@ export function stopMeasure(eventName: string) { performance.clearMarks(started); performance.clearMarks(completed); performance.clearMeasures(measured); + return measure; } catch (error) { console.error(`[Metrics] Failed to stopMeasure ${eventName}`, error); + return; } } diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index c9132a9415e..66c00150b0e 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -2,12 +2,14 @@ import { locationUtil } from '@grafana/data'; import { config, getBackendSrv, isFetchError, locationService } from '@grafana/runtime'; import { StateManagerBase } from 'app/core/services/StateManagerBase'; import { default as localStorageStore } from 'app/core/store'; +import { startMeasure, stopMeasure } from 'app/core/utils/metrics'; import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { DASHBOARD_FROM_LS_KEY, removeDashboardToFetchFromLocalStorage, } from 'app/features/dashboard/state/initDashboard'; +import { trackDashboardSceneLoaded } from 'app/features/dashboard/utils/tracking'; import { DashboardDTO, DashboardRoutes } from 'app/types'; import { PanelEditor } from '../panel-edit/PanelEditor'; @@ -26,6 +28,8 @@ export interface DashboardScenePageState { export const DASHBOARD_CACHE_TTL = 500; +const LOAD_SCENE_MEASUREMENT = 'loadDashboardScene'; + /** Only used by cache in loading home in DashboardPageProxy and initDashboard (Old arch), can remove this after old dashboard arch is gone */ export const HOME_DASHBOARD_CACHE_KEY = '__grafana_home_uid__'; @@ -167,6 +171,7 @@ export class DashboardScenePageStateManager extends StateManagerBase { - const unsetDashboardInteractionsScenesContext = DashboardInteractions.setScenesContext(); - - trackDashboardLoaded(model, model.version); - - return () => { - unsetDashboardInteractionsScenesContext(); - }; - }; -} - function registerPanelInteractionsReporter(scene: DashboardScene) { // Subscriptions set with subscribeToEvent are automatically unsubscribed when the scene deactivated scene.subscribeToEvent(UserActionEvent, (e) => { diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts index af312e80738..4d77b54728d 100644 --- a/public/app/features/dashboard/state/initDashboard.ts +++ b/public/app/features/dashboard/state/initDashboard.ts @@ -6,6 +6,7 @@ import { createErrorNotification } from 'app/core/copy/appNotification'; import { backendSrv } from 'app/core/services/backend_srv'; import { KeybindingSrv } from 'app/core/services/keybindingSrv'; import store from 'app/core/store'; +import { startMeasure, stopMeasure } from 'app/core/utils/metrics'; import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; import { DashboardSrv, getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; @@ -30,6 +31,8 @@ import { PanelModel } from './PanelModel'; import { emitDashboardViewEvent } from './analyticsProcessor'; import { dashboardInitCompleted, dashboardInitFailed, dashboardInitFetching, dashboardInitServices } from './reducers'; +const INIT_DASHBOARD_MEASUREMENT = 'initDashboard'; + export interface InitDashboardArgs { urlUid?: string; urlSlug?: string; @@ -174,7 +177,7 @@ const getQueriesByDatasource = ( */ export function initDashboard(args: InitDashboardArgs): ThunkResult { return async (dispatch, getState) => { - const initStart = performance.now(); + startMeasure(INIT_DASHBOARD_MEASUREMENT); // set fetching state dispatch(dashboardInitFetching()); @@ -287,8 +290,8 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult { }) ); - const duration = performance.now() - initStart; - trackDashboardLoaded(dashboard, duration, versionBeforeMigration); + const measure = stopMeasure(INIT_DASHBOARD_MEASUREMENT); + trackDashboardLoaded(dashboard, measure?.duration, versionBeforeMigration); // yay we are done dispatch(dashboardInitCompleted(dashboard)); diff --git a/public/app/features/dashboard/utils/tracking.test.ts b/public/app/features/dashboard/utils/tracking.test.ts index 79fc467fba9..9adb9e6d39b 100644 --- a/public/app/features/dashboard/utils/tracking.test.ts +++ b/public/app/features/dashboard/utils/tracking.test.ts @@ -37,6 +37,7 @@ describe('trackDashboardLoaded', () => { expect(reportInteractionSpy).toHaveBeenCalledWith('dashboards_init_dashboard_completed', { duration: 200, + isScene: false, uid: 'dashboard-123', title: 'Test Dashboard', schemaVersion: model.schemaVersion, // This value is based on public/app/features/dashboard/state/DashboardMigrator.ts#L81 diff --git a/public/app/features/dashboard/utils/tracking.ts b/public/app/features/dashboard/utils/tracking.ts index 5031a396072..a70c8b84ab4 100644 --- a/public/app/features/dashboard/utils/tracking.ts +++ b/public/app/features/dashboard/utils/tracking.ts @@ -1,23 +1,14 @@ +import { Panel, VariableModel } from '@grafana/schema/dist/esm/index'; +import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions'; -import { DashboardModel } from '../state'; +import { DashboardModel, PanelModel } from '../state'; -export function trackDashboardLoaded(dashboard: DashboardModel, duration: number, versionBeforeMigration?: number) { +export function trackDashboardLoaded(dashboard: DashboardModel, duration?: number, versionBeforeMigration?: number) { // Count the different types of variables - const variables = dashboard.templating.list - .map((v) => v.type) - .reduce((r: Record, k) => { - r[variableName(k)] = 1 + r[variableName(k)] || 1; - return r; - }, {}); - + const variables = getVariables(dashboard.templating.list); // Count the different types of panels - const panels = dashboard.panels - .map((p) => p.type) - .reduce((r: Record, p) => { - r[panelName(p)] = 1 + r[panelName(p)] || 1; - return r; - }, {}); + const panels = getPanelCounts(dashboard.panels); DashboardInteractions.dashboardInitialized({ uid: dashboard.uid, @@ -31,8 +22,49 @@ export function trackDashboardLoaded(dashboard: DashboardModel, duration: number settings_nowdelay: dashboard.timepicker.nowDelay, settings_livenow: !!dashboard.liveNow, duration, + isScene: false, }); } +export function trackDashboardSceneLoaded(dashboard: DashboardScene, duration?: number) { + const initialSaveModel = dashboard.getInitialSaveModel(); + if (initialSaveModel) { + const panels = getPanelCounts(initialSaveModel.panels || []); + const variables = getVariables(initialSaveModel.templating?.list || []); + DashboardInteractions.dashboardInitialized({ + uid: initialSaveModel.uid, + title: initialSaveModel.title, + theme: undefined, + schemaVersion: initialSaveModel.schemaVersion, + version_before_migration: initialSaveModel.version, + panels_count: initialSaveModel.panels?.length || 0, + ...panels, + ...variables, + settings_nowdelay: undefined, + settings_livenow: !!initialSaveModel.liveNow, + duration, + isScene: true, + }); + } +} + +function getPanelCounts(panels: Panel[] | PanelModel[]) { + return panels + .map((p) => p.type) + .reduce((r: Record, p) => { + r[panelName(p)] = 1 + r[panelName(p)] || 1; + return r; + }, {}); +} + +function getVariables(variableList: VariableModel[]) { + return variableList + .map((v) => v.type) + .reduce((r: Record, k) => { + r[variableName(k)] = 1 + r[variableName(k)] || 1; + return r; + }, {}); +} + const variableName = (type: string) => `variable_type_${type}_count`; const panelName = (type: string) => `panel_type_${type}_count`; From 1e1c62fef154b09deb96c0f15c107292a81c9d54 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 24 Apr 2024 12:05:20 +0200 Subject: [PATCH 082/222] FE Sandbox: Get plugin module path from bootdata instead of plugin settings (#86702) * FE Sandbox: Get plugin module path from bootdata instead of plugin settings * Remove unnecessary async/await --- .../features/plugins/sandbox/code_loader.ts | 39 +++++++++++++++++-- .../plugins/sandbox/distortion_map.ts | 27 +++++++------ .../plugins/sandbox/sandbox_components.tsx | 8 ++-- .../plugins/sandbox/sandbox_plugin_loader.ts | 12 +++--- public/app/features/plugins/sandbox/types.ts | 4 +- 5 files changed, 60 insertions(+), 30 deletions(-) diff --git a/public/app/features/plugins/sandbox/code_loader.ts b/public/app/features/plugins/sandbox/code_loader.ts index 3f2d614bf41..04bd418d33a 100644 --- a/public/app/features/plugins/sandbox/code_loader.ts +++ b/public/app/features/plugins/sandbox/code_loader.ts @@ -1,10 +1,11 @@ -import { PluginMeta, patchArrayVectorProrotypeMethods } from '@grafana/data'; +import { PluginType, patchArrayVectorProrotypeMethods } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { transformPluginSourceForCDN } from '../cdn/utils'; import { resolveWithCache } from '../loader/cache'; import { isHostedOnCDN, resolveModulePath } from '../loader/utils'; -import { SandboxEnvironment } from './types'; +import { SandboxEnvironment, SandboxPluginMeta } from './types'; function isSameDomainAsHost(url: string): boolean { const locationUrl = new URL(window.location.href); @@ -12,7 +13,7 @@ function isSameDomainAsHost(url: string): boolean { return locationUrl.host === paramUrl.host; } -export async function loadScriptIntoSandbox(url: string, meta: PluginMeta, sandboxEnv: SandboxEnvironment) { +export async function loadScriptIntoSandbox(url: string, sandboxEnv: SandboxEnvironment) { let scriptCode = ''; // same-domain @@ -46,7 +47,7 @@ export async function loadScriptIntoSandbox(url: string, meta: PluginMeta, sandb sandboxEnv.evaluate(scriptCode); } -export async function getPluginCode(meta: PluginMeta): Promise { +export async function getPluginCode(meta: SandboxPluginMeta): Promise { if (isHostedOnCDN(meta.module)) { // Load plugin from CDN, no need for "resolveWithCache" as CDN URLs already include the version const url = meta.module; @@ -88,3 +89,33 @@ export function patchSandboxEnvironmentPrototype(sandboxEnvironment: SandboxEnvi `${patchArrayVectorProrotypeMethods.toString()};${patchArrayVectorProrotypeMethods.name}()` ); } + +export function getPluginLoadData(pluginId: string): SandboxPluginMeta { + // find it in datasources + for (const datasource of Object.values(config.datasources)) { + if (datasource.type === pluginId) { + return datasource.meta; + } + } + + //find it in panels + for (const panel of Object.values(config.panels)) { + if (panel.id === pluginId) { + return panel; + } + } + + //find it in apps + //the information inside the apps object is more limited + for (const app of Object.values(config.apps)) { + if (app.id === pluginId) { + return { + id: pluginId, + type: PluginType.app, + module: app.path, + }; + } + } + + throw new Error(`Could not find plugin ${pluginId}`); +} diff --git a/public/app/features/plugins/sandbox/distortion_map.ts b/public/app/features/plugins/sandbox/distortion_map.ts index a883b9a763f..2989172874d 100644 --- a/public/app/features/plugins/sandbox/distortion_map.ts +++ b/public/app/features/plugins/sandbox/distortion_map.ts @@ -1,14 +1,13 @@ import { ProxyTarget } from '@locker/near-membrane-shared'; import { cloneDeep, isFunction } from 'lodash'; -import { PluginMeta } from '@grafana/data'; import { config } from '@grafana/runtime'; import { Monaco } from '@grafana/ui'; import { loadScriptIntoSandbox } from './code_loader'; import { forbiddenElements } from './constants'; import { recursivePatchObjectAsLiveTarget } from './document_sandbox'; -import { SandboxEnvironment } from './types'; +import { SandboxEnvironment, SandboxPluginMeta } from './types'; import { logWarning, unboxRegexesFromMembraneProxy } from './utils'; /** @@ -64,7 +63,7 @@ import { logWarning, unboxRegexesFromMembraneProxy } from './utils'; type DistortionMap = Map< unknown, - (originalAttrOrMethod: unknown, pluginMeta: PluginMeta, sandboxEnv?: SandboxEnvironment) => unknown + (originalAttrOrMethod: unknown, pluginMeta: SandboxPluginMeta, sandboxEnv?: SandboxEnvironment) => unknown >; const generalDistortionMap: DistortionMap = new Map(); @@ -91,7 +90,7 @@ export function getGeneralSandboxDistortionMap() { return generalDistortionMap; } -function failToSet(originalAttrOrMethod: unknown, meta: PluginMeta) { +function failToSet(originalAttrOrMethod: unknown, meta: SandboxPluginMeta) { logWarning(`Plugin ${meta.id} tried to set a sandboxed property`, { pluginId: meta.id, attrOrMethod: String(originalAttrOrMethod), @@ -112,7 +111,7 @@ function distortIframeAttributes(distortions: DistortionMap) { for (const property of iframeHtmlForbiddenProperties) { const descriptor = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, property); if (descriptor) { - function fail(originalAttrOrMethod: unknown, meta: PluginMeta) { + function fail(originalAttrOrMethod: unknown, meta: SandboxPluginMeta) { const pluginId = meta.id; logWarning(`Plugin ${pluginId} tried to access iframe.${property}`, { pluginId, @@ -146,7 +145,7 @@ function distortIframeAttributes(distortions: DistortionMap) { function distortConsole(distortions: DistortionMap) { const descriptor = Object.getOwnPropertyDescriptor(window, 'console'); if (descriptor?.value) { - function getSandboxConsole(originalAttrOrMethod: unknown, meta: PluginMeta) { + function getSandboxConsole(originalAttrOrMethod: unknown, meta: SandboxPluginMeta) { const pluginId = meta.id; // we don't monitor the console because we expect a high volume of calls if (monitorOnly) { @@ -175,7 +174,7 @@ function distortConsole(distortions: DistortionMap) { // set distortions to alert to always output to the console function distortAlert(distortions: DistortionMap) { - function getAlertDistortion(originalAttrOrMethod: unknown, meta: PluginMeta) { + function getAlertDistortion(originalAttrOrMethod: unknown, meta: SandboxPluginMeta) { const pluginId = meta.id; logWarning(`Plugin ${pluginId} accessed window.alert`, { pluginId, @@ -201,7 +200,7 @@ function distortAlert(distortions: DistortionMap) { } function distortInnerHTML(distortions: DistortionMap) { - function getInnerHTMLDistortion(originalMethod: unknown, meta: PluginMeta) { + function getInnerHTMLDistortion(originalMethod: unknown, meta: SandboxPluginMeta) { const pluginId = meta.id; return function innerHTMLDistortion(this: HTMLElement, ...args: string[]) { for (const arg of args) { @@ -246,7 +245,7 @@ function distortInnerHTML(distortions: DistortionMap) { } function distortCreateElement(distortions: DistortionMap) { - function getCreateElementDistortion(originalMethod: unknown, meta: PluginMeta) { + function getCreateElementDistortion(originalMethod: unknown, meta: SandboxPluginMeta) { const pluginId = meta.id; return function createElementDistortion(this: HTMLElement, arg?: string, options?: unknown) { if (arg && forbiddenElements.includes(arg)) { @@ -272,7 +271,7 @@ function distortCreateElement(distortions: DistortionMap) { } function distortInsert(distortions: DistortionMap) { - function getInsertDistortion(originalMethod: unknown, meta: PluginMeta) { + function getInsertDistortion(originalMethod: unknown, meta: SandboxPluginMeta) { const pluginId = meta.id; return function insertChildDistortion(this: HTMLElement, node?: Node, ref?: Node) { const nodeType = node?.nodeName?.toLowerCase() || ''; @@ -294,7 +293,7 @@ function distortInsert(distortions: DistortionMap) { }; } - function getinsertAdjacentElementDistortion(originalMethod: unknown, meta: PluginMeta) { + function getinsertAdjacentElementDistortion(originalMethod: unknown, meta: SandboxPluginMeta) { const pluginId = meta.id; return function insertAdjacentElementDistortion(this: HTMLElement, position?: string, node?: Node) { const nodeType = node?.nodeName?.toLowerCase() || ''; @@ -336,7 +335,7 @@ function distortInsert(distortions: DistortionMap) { // set distortions to append elements to the document function distortAppend(distortions: DistortionMap) { // append accepts an array of nodes to append https://developer.mozilla.org/en-US/docs/Web/API/Node/append - function getAppendDistortion(originalMethod: unknown, meta: PluginMeta) { + function getAppendDistortion(originalMethod: unknown, meta: SandboxPluginMeta) { const pluginId = meta.id; return function appendDistortion(this: HTMLElement, ...args: Node[]) { let acceptedNodes = args; @@ -363,7 +362,7 @@ function distortAppend(distortions: DistortionMap) { } // appendChild accepts a single node to add https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild - function getAppendChildDistortion(originalMethod: unknown, meta: PluginMeta, sandboxEnv?: SandboxEnvironment) { + function getAppendChildDistortion(originalMethod: unknown, meta: SandboxPluginMeta, sandboxEnv?: SandboxEnvironment) { const pluginId = meta.id; return function appendChildDistortion(this: HTMLElement, arg?: Node) { const nodeType = arg?.nodeName?.toLowerCase() || ''; @@ -383,7 +382,7 @@ function distortAppend(distortions: DistortionMap) { // this allows webpack chunks to be loaded into the sandbox // loadScriptIntoSandbox has restrictions on what scripts can be loaded if (sandboxEnv && arg && nodeType === 'script' && arg instanceof HTMLScriptElement) { - loadScriptIntoSandbox(arg.src, meta, sandboxEnv) + loadScriptIntoSandbox(arg.src, sandboxEnv) .then(() => { arg.onload?.call(arg, new Event('load')); }) diff --git a/public/app/features/plugins/sandbox/sandbox_components.tsx b/public/app/features/plugins/sandbox/sandbox_components.tsx index 9ff02f2da2f..6e26d86a37b 100644 --- a/public/app/features/plugins/sandbox/sandbox_components.tsx +++ b/public/app/features/plugins/sandbox/sandbox_components.tsx @@ -1,9 +1,9 @@ import { isFunction } from 'lodash'; import React, { ComponentType, FC } from 'react'; -import { GrafanaPlugin, PluginExtensionConfig, PluginMeta, PluginType } from '@grafana/data'; +import { GrafanaPlugin, PluginExtensionConfig, PluginType } from '@grafana/data'; -import { SandboxedPluginObject } from './types'; +import { SandboxPluginMeta, SandboxedPluginObject } from './types'; import { isSandboxedPluginObject } from './utils'; /** @@ -27,7 +27,7 @@ import { isSandboxedPluginObject } from './utils'; */ export async function sandboxPluginComponents( pluginExports: System.Module, - meta: PluginMeta + meta: SandboxPluginMeta ): Promise { if (!isSandboxedPluginObject(pluginExports)) { // we should monitor these cases. There should not be any plugins without a plugin export loaded inside the sandbox @@ -89,7 +89,7 @@ export async function sandboxPluginComponents( const withSandboxWrapper =

( WrappedComponent: ComponentType

, - pluginMeta: PluginMeta + pluginMeta: SandboxPluginMeta ): React.MemoExoticComponent> => { const WithWrapper = React.memo((props: P) => { return ( diff --git a/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts b/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts index 78619d3b93a..dc0b26dc2c0 100644 --- a/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts +++ b/public/app/features/plugins/sandbox/sandbox_plugin_loader.ts @@ -1,13 +1,11 @@ import createVirtualEnvironment from '@locker/near-membrane-dom'; import { ProxyTarget } from '@locker/near-membrane-shared'; -import { BootData, PluginMeta } from '@grafana/data'; +import { BootData } from '@grafana/data'; import { config } from '@grafana/runtime'; import { defaultTrustedTypesPolicy } from 'app/core/trustedTypePolicies'; -import { getPluginSettings } from '../pluginSettings'; - -import { getPluginCode, patchSandboxEnvironmentPrototype } from './code_loader'; +import { getPluginCode, getPluginLoadData, patchSandboxEnvironmentPrototype } from './code_loader'; import { getGeneralSandboxDistortionMap, distortLiveApis } from './distortion_map'; import { getSafeSandboxDomElement, @@ -19,7 +17,7 @@ import { } from './document_sandbox'; import { sandboxPluginDependencies } from './plugin_dependencies'; import { sandboxPluginComponents } from './sandbox_components'; -import { CompartmentDependencyModule, PluginFactoryFunction, SandboxEnvironment } from './types'; +import { CompartmentDependencyModule, PluginFactoryFunction, SandboxEnvironment, SandboxPluginMeta } from './types'; import { logError, logInfo } from './utils'; // Loads near membrane custom formatter for near membrane proxy objects. @@ -33,7 +31,7 @@ const pluginLogCache: Record = {}; export async function importPluginModuleInSandbox({ pluginId }: { pluginId: string }): Promise { patchWebAPIs(); try { - const pluginMeta = await getPluginSettings(pluginId); + const pluginMeta = getPluginLoadData(pluginId); if (!pluginImportCache.has(pluginId)) { pluginImportCache.set(pluginId, doImportPluginModuleInSandbox(pluginMeta)); } @@ -48,7 +46,7 @@ export async function importPluginModuleInSandbox({ pluginId }: { pluginId: stri } } -async function doImportPluginModuleInSandbox(meta: PluginMeta): Promise { +async function doImportPluginModuleInSandbox(meta: SandboxPluginMeta): Promise { logInfo('Loading with sandbox', { pluginId: meta.id, }); diff --git a/public/app/features/plugins/sandbox/types.ts b/public/app/features/plugins/sandbox/types.ts index aacd915ce0d..3b9e104f801 100644 --- a/public/app/features/plugins/sandbox/types.ts +++ b/public/app/features/plugins/sandbox/types.ts @@ -1,6 +1,6 @@ import createVirtualEnvironment from '@locker/near-membrane-dom'; -import { GrafanaPlugin } from '@grafana/data'; +import { GrafanaPlugin, PluginMeta } from '@grafana/data'; export type CompartmentDependencyModule = unknown; export type PluginFactoryFunction = (...args: CompartmentDependencyModule[]) => SandboxedPluginObject; @@ -10,3 +10,5 @@ export type SandboxedPluginObject = { }; export type SandboxEnvironment = ReturnType; + +export type SandboxPluginMeta = Pick; From 60ed6bfc3320c2f8ee59737ab6e2aa1ee95861cc Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 24 Apr 2024 12:23:08 +0200 Subject: [PATCH 083/222] Search: Fix slow query when user does not have roles assigned (#86791) * Search: Fix slow query when user does not have roles assigned * Check all required actions and skip if not found --- pkg/services/sqlstore/permissions/dashboard.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/permissions/dashboard.go b/pkg/services/sqlstore/permissions/dashboard.go index 38f8ad92a60..293a19b6bcf 100644 --- a/pkg/services/sqlstore/permissions/dashboard.go +++ b/pkg/services/sqlstore/permissions/dashboard.go @@ -114,8 +114,21 @@ func (f *accessControlDashboardPermissionFilter) Where() (string, []any) { return f.where.string, f.where.params } +// Check if user has no permissions required for search to skip expensive query +func (f *accessControlDashboardPermissionFilter) hasRequiredActions() bool { + permissions := f.user.GetPermissions() + requiredActions := append(f.folderActions, f.dashboardActions...) + for _, action := range requiredActions { + if _, ok := permissions[action]; ok { + return true + } + } + + return false +} + func (f *accessControlDashboardPermissionFilter) buildClauses() { - if f.user == nil || f.user.IsNil() || len(f.user.GetPermissions()) == 0 { + if f.user == nil || f.user.IsNil() || !f.hasRequiredActions() { f.where = clause{string: "(1 = 0)"} return } From fd1bf66d86b3b5f371cdd41db7d7a6b130784373 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Wed, 24 Apr 2024 12:27:28 +0200 Subject: [PATCH 084/222] Plugins: Selectively load plugins using script tags (#85750) * feat(plugins): introduce logic to selectively load fe plugins via script tags * feat(plugins): extend cache to store isAngular flag. use isAngular in shouldFetch * revert(plugins): remove unused prepareImport from SystemJSWithLoaderHooks type * fix(plugins): cache[path] maybe undefined if not registered or invalidated * Update public/app/features/plugins/plugin_loader.ts Co-authored-by: Levente Balogh --------- Co-authored-by: Levente Balogh --- public/app/features/plugins/loader/cache.ts | 22 +++++++++++++++----- public/app/features/plugins/loader/types.ts | 2 +- public/app/features/plugins/plugin_loader.ts | 16 ++++++++------ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/public/app/features/plugins/loader/cache.ts b/public/app/features/plugins/loader/cache.ts index 3db74fd2a70..7bc30c405f9 100644 --- a/public/app/features/plugins/loader/cache.ts +++ b/public/app/features/plugins/loader/cache.ts @@ -1,17 +1,22 @@ import { clearPluginSettingsCache } from '../pluginSettings'; -const cache: Record = {}; +const cache: Record = {}; const initializedAt: number = Date.now(); type CacheablePlugin = { path: string; version: string; + isAngular?: boolean; }; -export function registerPluginInCache({ path, version }: CacheablePlugin): void { +export function registerPluginInCache({ path, version, isAngular }: CacheablePlugin): void { const key = extractPath(path); if (key && !cache[key]) { - cache[key] = encodeURI(version); + cache[key] = { + version: encodeURI(version), + isAngular, + path, + }; } } @@ -28,12 +33,19 @@ export function resolveWithCache(url: string, defaultBust = initializedAt): stri if (!path) { return `${url}?_cache=${defaultBust}`; } - - const version = cache[path]; + const version = cache[path]?.version; const bust = version || defaultBust; return `${url}?_cache=${bust}`; } +export function getPluginFromCache(path: string): CacheablePlugin | undefined { + const key = extractPath(path); + if (!key) { + return; + } + return cache[key]; +} + function extractPath(address: string): string | undefined { const match = /\/?.+\/(plugins\/.+\/module)\.js/i.exec(address); if (!match) { diff --git a/public/app/features/plugins/loader/types.ts b/public/app/features/plugins/loader/types.ts index 023f6a3986c..108f034d0b4 100644 --- a/public/app/features/plugins/loader/types.ts +++ b/public/app/features/plugins/loader/types.ts @@ -1,7 +1,7 @@ // Extend the System type with the loader hooks we use // to provide backwards compatibility with older version of Systemjs export type SystemJSWithLoaderHooks = typeof System & { - shouldFetch: () => Boolean; + shouldFetch: (url: string) => Boolean; fetch: (url: string, options?: Record) => Promise; onload: (err: unknown, id: string) => void; }; diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 0593d1e3eea..86fa82d8897 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -11,14 +11,14 @@ import { DataQuery } from '@grafana/schema'; import { GenericDataSourcePlugin } from '../datasources/types'; import builtInPlugins from './built_in_plugins'; -import { registerPluginInCache } from './loader/cache'; +import { getPluginFromCache, registerPluginInCache } from './loader/cache'; // SystemJS has to be imported before the sharedDependenciesMap import { SystemJS } from './loader/systemjs'; // eslint-disable-next-line import/order import { sharedDependenciesMap } from './loader/sharedDependencies'; import { decorateSystemJSFetch, decorateSystemJSResolve, decorateSystemJsOnload } from './loader/systemjsHooks'; import { SystemJSWithLoaderHooks } from './loader/types'; -import { buildImportMap, resolveModulePath } from './loader/utils'; +import { buildImportMap, isHostedOnCDN, resolveModulePath } from './loader/utils'; import { importPluginModuleInSandbox } from './sandbox/sandbox_plugin_loader'; import { isFrontendSandboxSupported } from './sandbox/utils'; @@ -28,9 +28,13 @@ SystemJS.addImportMap({ imports }); const systemJSPrototype: SystemJSWithLoaderHooks = SystemJS.constructor.prototype; -// Monaco Editors reliance on RequireJS means we need to transform -// the content of the plugin code at runtime which can only be done with fetch/eval. -systemJSPrototype.shouldFetch = () => true; +// This instructs SystemJS to load a plugin using fetch and eval if it returns a truthy value, otherwise it will load the plugin using a script tag. +// We only want to fetch and eval plugins that are hosted on a CDN or are Angular plugins. +systemJSPrototype.shouldFetch = function (url) { + const pluginInfo = getPluginFromCache(url); + + return isHostedOnCDN(url) || Boolean(pluginInfo?.isAngular); +}; const originalImport = systemJSPrototype.import; // Hook Systemjs import to support plugins that only have a default export. @@ -68,7 +72,7 @@ export async function importPluginModule({ isAngular?: boolean; }): Promise { if (version) { - registerPluginInCache({ path, version }); + registerPluginInCache({ path, version, isAngular }); } const builtIn = builtInPlugins[path]; From f382bd402c9d25408890a076165ce6ba32748f86 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Wed, 24 Apr 2024 12:08:19 +0100 Subject: [PATCH 085/222] Chore: Remove i18n psuedo precommit hook (#86840) Remove i18n psuedo precommit hook --- lefthook.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index 9c3bed5f538..26077d9fb44 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -23,11 +23,6 @@ pre-commit: yarn prettier --write {staged_files} stage_fixed: true - internationalization: - glob: 'public/locales/en-US/grafana.json' - run: yarn i18n:pseudo - stage_fixed: true - other-format: glob: '*.{json,scss,md,mdx}' run: yarn prettier --write {staged_files} From 3e450ec4bff3f971f6b885b6282bce8055a02a1b Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Wed, 24 Apr 2024 13:46:40 +0200 Subject: [PATCH 086/222] Panel: Keyboard focus shortcuts prioritization (#86772) * Panel: Keyboard shortcuts prio * Remove redundant Array.from * Simplify * Handle Scenes use case --- public/app/core/services/withFocusedPanelId.ts | 8 ++++++++ .../dashboard-scene/scene/keyboardShortcuts.ts | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/public/app/core/services/withFocusedPanelId.ts b/public/app/core/services/withFocusedPanelId.ts index 857144ee706..9c67c0c71ca 100644 --- a/public/app/core/services/withFocusedPanelId.ts +++ b/public/app/core/services/withFocusedPanelId.ts @@ -2,6 +2,14 @@ export function withFocusedPanel(fn: (panelId: number) => void) { return () => { const elements = document.querySelectorAll(':hover'); + // Handle keyboard focus first + const focusedGridElement = document.activeElement?.closest('[data-panelid]'); + + if (focusedGridElement instanceof HTMLElement && focusedGridElement.dataset?.panelid) { + fn(parseInt(focusedGridElement.dataset?.panelid, 10)); + return; + } + for (let i = elements.length - 1; i > 0; i--) { const element = elements[i]; if (element instanceof HTMLElement && element.dataset?.panelid) { diff --git a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts index 99123eaf9ca..3a111f3421a 100644 --- a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts +++ b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts @@ -146,6 +146,16 @@ export function setupKeyboardShortcuts(scene: DashboardScene) { export function withFocusedPanel(scene: DashboardScene, fn: (vizPanel: VizPanel) => void) { return () => { const elements = document.querySelectorAll(':hover'); + const focusedGridElement = document.activeElement?.closest('[data-viz-panel-key]'); + + if (focusedGridElement instanceof HTMLElement && focusedGridElement.dataset?.vizPanelKey) { + const panelKey = focusedGridElement.dataset?.vizPanelKey; + const vizPanel = sceneGraph.findObject(scene, (o) => o.state.key === panelKey); + if (vizPanel && vizPanel instanceof VizPanel) { + fn(vizPanel); + return; + } + } for (let i = elements.length - 1; i > 0; i--) { const element = elements[i]; From 38917c4e79ee44338e28d1ebad861c0a0ae36ee5 Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Wed, 24 Apr 2024 14:54:30 +0300 Subject: [PATCH 087/222] Fix home breadcrumbs (#86786) * fix home breadcrumbs * fix homepage gap * fix tests --- .../dashboard-scene/pages/DashboardScenePage.test.tsx | 4 ++-- .../features/dashboard-scene/scene/DashboardScene.tsx | 1 + .../dashboard-scene/scene/DashboardSceneRenderer.tsx | 10 +++++++--- .../app/features/dashboard-scene/utils/urlBuilders.ts | 6 ++++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx index e6953c5e135..3b8e8699882 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx +++ b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx @@ -124,7 +124,7 @@ describe('DashboardScenePage', () => { locationService.push('/'); getDashboardScenePageStateManager().clearDashboardCache(); loadDashboardMock.mockClear(); - loadDashboardMock.mockResolvedValue({ dashboard: simpleDashboard, meta: {} }); + loadDashboardMock.mockResolvedValue({ dashboard: simpleDashboard, meta: { slug: '123' } }); // hacky way because mocking autosizer does not work Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { configurable: true, value: 1000 }); Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { configurable: true, value: 1000 }); @@ -241,7 +241,7 @@ describe('DashboardScenePage', () => { }); it('is in edit mode when coming from explore to an existing dashboard', async () => { - store.setObject(DASHBOARD_FROM_LS_KEY, { dashboard: simpleDashboard }); + store.setObject(DASHBOARD_FROM_LS_KEY, { dashboard: simpleDashboard, meta: { slug: '123' } }); setup(); diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index eebc3be983e..3358f3f696c 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -396,6 +396,7 @@ export class DashboardScene extends SceneObjectBase { slug: meta.slug, currentQueryParams: location.search, updateQuery: { viewPanel: null, inspect: null, editview: null, editPanel: null, tab: null }, + isHomeDashboard: !meta.url && !meta.slug && !meta.isNew, }), }; diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx index 7a0ab70610e..694c002720b 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx @@ -14,7 +14,7 @@ import { DashboardScene } from './DashboardScene'; import { NavToolbarActions } from './NavToolbarActions'; export function DashboardSceneRenderer({ model }: SceneComponentProps) { - const { controls, overlay, editview, editPanel, isEmpty, scopes } = model.useState(); + const { controls, overlay, editview, editPanel, isEmpty, scopes, meta } = model.useState(); const { isExpanded: isScopesExpanded } = scopes?.useState() ?? {}; const styles = useStyles2(getStyles); const location = useLocation(); @@ -22,6 +22,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps {scopes && } - {controls && ( + {!isHomePage && controls && (

@@ -62,7 +63,7 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps )} -
+
<>{isEmpty && emptyState} {withPanels}
@@ -115,6 +116,9 @@ function getStyles(theme: GrafanaTheme2) { controlsWrapperWithScopes: css({ padding: theme.spacing(2, 2, 2, 0), }), + homePagePadding: css({ + padding: theme.spacing(2, 2), + }), canvasContent: css({ label: 'canvas-content', display: 'flex', diff --git a/public/app/features/dashboard-scene/utils/urlBuilders.ts b/public/app/features/dashboard-scene/utils/urlBuilders.ts index e3e7ae8331c..fc334be29fb 100644 --- a/public/app/features/dashboard-scene/utils/urlBuilders.ts +++ b/public/app/features/dashboard-scene/utils/urlBuilders.ts @@ -22,6 +22,8 @@ export interface DashboardUrlOptions { absolute?: boolean; // Add tz to query params timeZone?: string; + // Check if we are on the home dashboard + isHomeDashboard?: boolean; } export function getDashboardUrl(options: DashboardUrlOptions) { @@ -54,6 +56,10 @@ export function getDashboardUrl(options: DashboardUrlOptions) { }; } + if (options.isHomeDashboard) { + path = '/'; + } + const params = options.currentQueryParams ? locationSearchToObject(options.currentQueryParams) : {}; if (options.updateQuery) { From 5dd8353ab16547b7e97e8a340c20a97d22560086 Mon Sep 17 00:00:00 2001 From: Khushi Jain Date: Wed, 24 Apr 2024 18:46:19 +0530 Subject: [PATCH 088/222] Dashboards: Migrate from aria-label e2e selectors to data-testid (#78536) * Dashboards: Migrate from aria-label e2e selectors to data-testid * more changes * addPanelwidget * Test: Update .betterer.results * refactor: fix e2e tests * refactor: fix failing test * refactor: update plugin-e2e after adding selector changes to the packege --------- Co-authored-by: Laura Benz <48948963+L-M-K-B@users.noreply.github.com> Co-authored-by: Laura Benz --- .betterer.results | 34 +++++-------------- e2e/various-suite/filter-annotations.spec.ts | 12 ++++--- package.json | 2 +- .../src/selectors/components.ts | 19 ++++++----- .../src/selectors/pages.ts | 10 +++--- .../inspect/InspectJsonTab.tsx | 2 +- .../AnnotationSettingsEdit.tsx | 8 ++--- .../components/PanelEditor/OptionsPane.tsx | 2 +- .../PanelEditor/OptionsPaneCategory.tsx | 5 ++- .../PanelEditor/OptionsPaneOptions.test.tsx | 2 +- .../components/PanelEditor/PanelEditor.tsx | 6 ++-- .../app/features/inspector/InspectJSONTab.tsx | 2 +- yarn.lock | 10 +++--- 13 files changed, 49 insertions(+), 65 deletions(-) diff --git a/.betterer.results b/.betterer.results index b1db9758f60..1fec2f3f3ed 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2408,9 +2408,6 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/inspect/HelpWizard/utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx:5381": [ - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] - ], "public/app/features/dashboard-scene/pages/DashboardScenePage.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], @@ -2518,11 +2515,7 @@ exports[`better eslint`] = { ], "public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"] + [0, 0, 0, "Styles should be written using objects.", "1"] ], "public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] @@ -2620,15 +2613,10 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "2"] ], "public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx:5381": [ - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], + [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"] - ], - "public/app/features/dashboard/components/PanelEditor/OptionsPaneCategory.tsx:5381": [ - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] + [0, 0, 0, "Styles should be written using objects.", "3"] ], "public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], @@ -2658,20 +2646,17 @@ exports[`better eslint`] = { ], "public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"], - [0, 0, 0, "Do not use any type assertions.", "4"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Styles should be written using objects.", "2"], + [0, 0, 0, "Styles should be written using objects.", "3"], + [0, 0, 0, "Styles should be written using objects.", "4"], [0, 0, 0, "Styles should be written using objects.", "5"], [0, 0, 0, "Styles should be written using objects.", "6"], [0, 0, 0, "Styles should be written using objects.", "7"], [0, 0, 0, "Styles should be written using objects.", "8"], [0, 0, 0, "Styles should be written using objects.", "9"], [0, 0, 0, "Styles should be written using objects.", "10"], - [0, 0, 0, "Styles should be written using objects.", "11"], - [0, 0, 0, "Styles should be written using objects.", "12"], - [0, 0, 0, "Styles should be written using objects.", "13"], - [0, 0, 0, "Styles should be written using objects.", "14"] + [0, 0, 0, "Styles should be written using objects.", "11"] ], "public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], @@ -3691,9 +3676,6 @@ exports[`better eslint`] = { "public/app/features/inspector/InspectDataTab.tsx:5381": [ [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] ], - "public/app/features/inspector/InspectJSONTab.tsx:5381": [ - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] - ], "public/app/features/inspector/InspectStatsTab.tsx:5381": [ [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] diff --git a/e2e/various-suite/filter-annotations.spec.ts b/e2e/various-suite/filter-annotations.spec.ts index 23d64eb0826..26cf83f00e2 100644 --- a/e2e/various-suite/filter-annotations.spec.ts +++ b/e2e/various-suite/filter-annotations.spec.ts @@ -18,19 +18,21 @@ describe('Annotations filtering', () => { .should('be.visible') .within(() => { // All panels - e2e.components.Annotations.annotationsTypeInput().click({ force: true }).type('All panels{enter}'); + e2e.components.Annotations.annotationsTypeInput().find('input').type('All panels{enter}', { force: true }); e2e.components.Annotations.annotationsChoosePanelInput().should('not.exist'); // All panels except - e2e.components.Annotations.annotationsTypeInput().click({ force: true }).type('All panels except{enter}'); + e2e.components.Annotations.annotationsTypeInput() + .find('input') + .type('All panels except{enter}', { force: true }); e2e.components.Annotations.annotationsChoosePanelInput().should('be.visible'); // Selected panels - e2e.components.Annotations.annotationsTypeInput().click({ force: true }).type('Selected panels{enter}'); + e2e.components.Annotations.annotationsTypeInput().find('input').type('Selected panels{enter}', { force: true }); e2e.components.Annotations.annotationsChoosePanelInput() .should('be.visible') - .click({ force: true }) - .type('Panel two{enter}'); + .find('input') + .type('Panel two{enter}', { force: true }); }); e2e.pages.Dashboard.Settings.Annotations.NewAnnotation.previewInDashboard().click({ force: true }); diff --git a/package.json b/package.json index 0db6125d884..ded110dd49a 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@emotion/eslint-plugin": "11.11.0", "@grafana/eslint-config": "7.0.0", "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules", - "@grafana/plugin-e2e": "1.1.1", + "@grafana/plugin-e2e": "1.2.0", "@grafana/tsconfig": "^1.3.0-rc1", "@manypkg/get-packages": "^2.2.0", "@playwright/test": "1.43.1", diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index ccfef3f8388..7dd6dd3ecb3 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -218,22 +218,22 @@ export const Components = { }, PanelEditor: { General: { - content: 'Panel editor content', + content: 'data-testid Panel editor content', }, OptionsPane: { - content: 'Panel editor option pane content', + content: 'data-testid Panel editor option pane content', select: 'Panel editor option pane select', fieldLabel: (type: string) => `${type} field property editor`, fieldInput: (title: string) => `data-testid Panel editor option pane field input ${title}`, }, // not sure about the naming *DataPane* DataPane: { - content: 'Panel editor data pane content', + content: 'data-testid Panel editor data pane content', }, applyButton: 'data-testid Apply changes and go back to dashboard', toggleVizPicker: 'data-testid toggle-viz-picker', toggleVizOptions: 'data-testid toggle-viz-options', - toggleTableView: 'toggle-table-view', + toggleTableView: 'data-testid toggle-table-view', // [Geomap] Map controls showZoomField: 'Map controls Show zoom control field property editor', @@ -252,7 +252,7 @@ export const Components = { content: 'Panel inspector Stats content', }, Json: { - content: 'Panel inspector Json content', + content: 'data-testid Panel inspector Json content', }, Query: { content: 'Panel inspector Query content', @@ -377,8 +377,9 @@ export const Components = { backArrow: 'data-testid Go Back', }, OptionsGroup: { - group: (title?: string) => (title ? `Options group ${title}` : 'Options group'), - toggle: (title?: string) => (title ? `Options group ${title} toggle` : 'Options group toggle'), + group: (title?: string) => (title ? `data-testid Options group ${title}` : 'data-testid Options group'), + toggle: (title?: string) => + title ? `data-testid Options group ${title} toggle` : 'data-testid Options group toggle', }, PluginVisualization: { item: (title: string) => `Plugin visualization item ${title}`, @@ -535,8 +536,8 @@ export const Components = { variableOption: 'data-testid variable-option', }, Annotations: { - annotationsTypeInput: 'annotations-type-input', - annotationsChoosePanelInput: 'choose-panels-input', + annotationsTypeInput: 'data-testid annotations-type-input', + annotationsChoosePanelInput: 'data-testid choose-panels-input', editor: { testButton: 'data-testid annotations-test-button', resultContainer: 'data-testid annotations-query-result-container', diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 5368be3830a..fabdda9e0b8 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -43,9 +43,9 @@ export const Pages = { AddDashboard: { url: '/dashboard/new', itemButton: (title: string) => `data-testid ${title}`, - addNewPanel: 'Add new panel', - addNewRow: 'Add new row', - addNewPanelLibrary: 'Add new panel from panel library', + addNewPanel: 'data-testid Add new panel', + addNewRow: 'data-testid Add new row', + addNewPanelLibrary: 'data-testid Add new panel from panel library', }, Dashboard: { url: (uid: string) => `/d/${uid}`, @@ -103,11 +103,11 @@ export const Pages = { annotations: 'data-testid list-annotations', }, Settings: { - name: 'Annotations settings name input', + name: 'data-testid Annotations settings name input', }, NewAnnotation: { panelFilterSelect: 'data-testid annotations-panel-filter', - showInLabel: 'show-in-label', + showInLabel: 'data-testid show-in-label', previewInDashboard: 'data-testid annotations-preview', delete: 'data-testid annotations-delete', apply: 'data-testid annotations-apply', diff --git a/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx b/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx index ce9e21371f0..e43ac6f5d0f 100644 --- a/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx +++ b/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx @@ -163,7 +163,7 @@ export class InspectJsonTab extends SceneObjectBase { return (
-
+
{ - + <> Date: Wed, 24 Apr 2024 15:37:22 +0200 Subject: [PATCH 089/222] Plugin preloading: Fix performance measurement (#86855) fix: measuring plugin preload performance --- public/app/app.ts | 2 +- public/app/features/plugins/pluginPreloader.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/public/app/app.ts b/public/app/app.ts index 56fa4405843..de0f8814d68 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -222,7 +222,7 @@ export class GrafanaApp { const appPlugins = Object.values(config.apps).filter((app) => !awaitedAppPluginIds.includes(app.id)); preloadPlugins(appPlugins, extensionsRegistry); - await preloadPlugins(awaitedAppPlugins, extensionsRegistry); + await preloadPlugins(awaitedAppPlugins, extensionsRegistry, 'frontend_awaited_plugins_preload'); } setPluginExtensionGetter(createPluginExtensionsGetter(extensionsRegistry)); diff --git a/public/app/features/plugins/pluginPreloader.ts b/public/app/features/plugins/pluginPreloader.ts index 25ea2f1f11a..1a00a69716d 100644 --- a/public/app/features/plugins/pluginPreloader.ts +++ b/public/app/features/plugins/pluginPreloader.ts @@ -12,8 +12,12 @@ export type PluginPreloadResult = { extensionConfigs: PluginExtensionConfig[]; }; -export async function preloadPlugins(apps: AppPluginConfig[] = [], registry: ReactivePluginExtensionsRegistry) { - startMeasure('frontend_plugins_preload'); +export async function preloadPlugins( + apps: AppPluginConfig[] = [], + registry: ReactivePluginExtensionsRegistry, + eventName = 'frontend_plugins_preload' +) { + startMeasure(eventName); const promises = apps.filter((config) => config.preload).map((config) => preload(config)); const preloadedPlugins = await Promise.all(promises); @@ -21,7 +25,7 @@ export async function preloadPlugins(apps: AppPluginConfig[] = [], registry: Rea registry.register(preloadedPlugin); } - stopMeasure('frontend_plugins_preload'); + stopMeasure(eventName); } async function preload(config: AppPluginConfig): Promise { From a5a3ee9fa3f6171b87e2e804e4a4b9dd29ed7be0 Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com> Date: Wed, 24 Apr 2024 15:25:43 +0100 Subject: [PATCH 090/222] SQLStore: Disable redundant create and drop unique index migrations on dashboard table (#86857) SQLStore: Disable create and drop unique index migrations --- .../migrations/folder_uid_migrator.go | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/pkg/services/dashboards/database/migrations/folder_uid_migrator.go b/pkg/services/dashboards/database/migrations/folder_uid_migrator.go index 4db022cfd81..fd1a075c82f 100644 --- a/pkg/services/dashboards/database/migrations/folder_uid_migrator.go +++ b/pkg/services/dashboards/database/migrations/folder_uid_migrator.go @@ -5,6 +5,18 @@ import ( "xorm.io/xorm" ) +type DummyMigration struct { + migrator.MigrationBase +} + +func (m *DummyMigration) SQL(dialect migrator.Dialect) string { + return "code migration" +} + +func (m *DummyMigration) Exec(sess *xorm.Session, mgrtr *migrator.Migrator) error { + return nil +} + // FolderUIDMigration is a code migration that populates folder_uid column type FolderUIDMigration struct { migrator.MigrationBase @@ -78,17 +90,13 @@ func AddDashboardFolderMigrations(mg *migrator.Migrator) { mg.AddMigration("Populate dashboard folder_uid column", &FolderUIDMigration{}) - mg.AddMigration("Add unique index for dashboard_org_id_folder_uid_title", migrator.NewAddIndexMigration(migrator.Table{Name: "dashboard"}, &migrator.Index{ - Cols: []string{"org_id", "folder_uid", "title"}, Type: migrator.UniqueIndex, - })) + mg.AddMigration("Add unique index for dashboard_org_id_folder_uid_title", &DummyMigration{}) mg.AddMigration("Delete unique index for dashboard_org_id_folder_id_title", migrator.NewDropIndexMigration(migrator.Table{Name: "dashboard"}, &migrator.Index{ Cols: []string{"org_id", "folder_id", "title"}, Type: migrator.UniqueIndex, })) - mg.AddMigration("Delete unique index for dashboard_org_id_folder_uid_title", migrator.NewDropIndexMigration(migrator.Table{Name: "dashboard"}, &migrator.Index{ - Cols: []string{"org_id", "folder_uid", "title"}, Type: migrator.UniqueIndex, - })) + mg.AddMigration("Delete unique index for dashboard_org_id_folder_uid_title", &DummyMigration{}) mg.AddMigration("Add unique index for dashboard_org_id_folder_uid_title_is_folder", migrator.NewAddIndexMigration(migrator.Table{Name: "dashboard"}, &migrator.Index{ Cols: []string{"org_id", "folder_uid", "title", "is_folder"}, Type: migrator.UniqueIndex, From c965c279943b12bf27598e736a8ec1a2420174f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 24 Apr 2024 16:36:58 +0200 Subject: [PATCH 091/222] DataTrails: Exploring alternatives to history issues (#86843) * DataTrails: Exploring alternatives to history issues * incorporated unit tests from #86817 and #86741 --------- Co-authored-by: Darren Janeczek --- public/app/features/trails/DataTrail.test.tsx | 276 +++++++++++++++++- public/app/features/trails/DataTrail.tsx | 103 +++---- .../app/features/trails/DataTrailsHistory.tsx | 7 +- 3 files changed, 318 insertions(+), 68 deletions(-) diff --git a/public/app/features/trails/DataTrail.test.tsx b/public/app/features/trails/DataTrail.test.tsx index 9b62ddeff41..a6ccc31f354 100644 --- a/public/app/features/trails/DataTrail.test.tsx +++ b/public/app/features/trails/DataTrail.test.tsx @@ -1,4 +1,5 @@ import { locationService, setDataSourceSrv } from '@grafana/runtime'; +import { AdHocFiltersVariable, sceneGraph } from '@grafana/scenes'; import { MockDataSourceSrv, mockDataSource } from '../alerting/unified/mocks'; import { DataSourceType } from '../alerting/unified/utils/datasource'; @@ -7,7 +8,7 @@ import { activateFullSceneTree } from '../dashboard-scene/utils/test-utils'; import { DataTrail } from './DataTrail'; import { MetricScene } from './MetricScene'; import { MetricSelectScene } from './MetricSelectScene'; -import { MetricSelectedEvent } from './shared'; +import { MetricSelectedEvent, VAR_FILTERS } from './shared'; describe('DataTrail', () => { beforeAll(() => { @@ -61,10 +62,6 @@ describe('DataTrail', () => { expect(trail.state.history.state.steps[1].type).toBe('metric'); }); - it('Should set history current step to 1', () => { - expect(trail.state.history.state.currentStep).toBe(1); - }); - it('Should set history currentStep to 1', () => { expect(trail.state.history.state.currentStep).toBe(1); }); @@ -73,6 +70,10 @@ describe('DataTrail', () => { expect(trail.state.history.state.steps[1].parentIndex).toBe(0); }); + it('Should have time range `from` be default "now-6h"', () => { + expect(trail.state.$timeRange?.state.from).toBe('now-6h'); + }); + describe('And browser back button is pressed', () => { locationService.getHistory().goBack(); @@ -81,6 +82,271 @@ describe('DataTrail', () => { expect(pathname).toEqual(preTrailUrl); }); }); + + describe('And when changing the time range `from` to "now-1h"', () => { + beforeEach(() => { + trail.state.$timeRange?.setState({ from: 'now-1h' }); + }); + + it('should sync state with url', () => { + expect(locationService.getSearchObject().from).toBe('now-1h'); + }); + + it('should add history step', () => { + expect(trail.state.history.state.steps[2].type).toBe('time'); + }); + + it('Should set history currentStep to 2', () => { + expect(trail.state.history.state.currentStep).toBe(2); + }); + + it('Should set history step 2 parentIndex to 1', () => { + expect(trail.state.history.state.steps[2].parentIndex).toBe(1); + }); + + it('Should have time range `from` be updated "now-1h"', () => { + expect(trail.state.$timeRange?.state.from).toBe('now-1h'); + }); + + it('Previous history step should have previous default `from` of "now-6h"', () => { + expect(trail.state.history.state.steps[1].trailState.$timeRange?.state.from).toBe('now-6h'); + }); + + it('Current history step should have new `from` of "now-1h"', () => { + expect(trail.state.history.state.steps[2].trailState.$timeRange?.state.from).toBe('now-1h'); + }); + + describe('And when traversing back to step 1', () => { + beforeEach(() => { + trail.state.history.goBackToStep(1); + }); + + it('Should set history currentStep to 1', () => { + expect(trail.state.history.state.currentStep).toBe(1); + }); + + it('should sync state with url', () => { + expect(locationService.getSearchObject().from).toBe('now-6h'); + }); + + it('Should have time range `from` be set back to "now-6h"', () => { + expect(trail.state.$timeRange?.state.from).toBe('now-6h'); + }); + + describe('And then when changing the time range `from` to "now-15m"', () => { + beforeEach(() => { + trail.state.$timeRange?.setState({ from: 'now-15m' }); + }); + + it('should sync state with url', () => { + expect(locationService.getSearchObject().from).toBe('now-15m'); + }); + + it('should add history step', () => { + expect(trail.state.history.state.steps[3].type).toBe('time'); + }); + + it('Should set history currentStep to 3', () => { + expect(trail.state.history.state.currentStep).toBe(3); + }); + + it('Should set history step 3 parentIndex to 1', () => { + expect(trail.state.history.state.steps[3].parentIndex).toBe(1); + }); + + it('Should have time range `from` be updated "now-15m"', () => { + expect(trail.state.$timeRange?.state.from).toBe('now-15m'); + }); + + it('History step 1 (parent) should have previous default `from` of "now-6h"', () => { + expect(trail.state.history.state.steps[1].trailState.$timeRange?.state.from).toBe('now-6h'); + }); + + it('History step 2 should still have `from` of "now-1h"', () => { + expect(trail.state.history.state.steps[2].trailState.$timeRange?.state.from).toBe('now-1h'); + }); + + describe('And then when returning again to step 1', () => { + beforeEach(() => { + trail.state.history.goBackToStep(1); + }); + + it('Should set history currentStep to 1', () => { + expect(trail.state.history.state.currentStep).toBe(1); + }); + + it('should sync state with url', () => { + expect(locationService.getSearchObject().from).toBe('now-6h'); + }); + + it('History step 1 (parent) should have previous default `from` of "now-6h"', () => { + expect(trail.state.history.state.steps[1].trailState.$timeRange?.state.from).toBe('now-6h'); + }); + + it('History step 2 should still have `from` of "now-1h"', () => { + expect(trail.state.history.state.steps[2].trailState.$timeRange?.state.from).toBe('now-1h'); + }); + + it('History step 3 should still have `from` of "now-15m"', () => { + expect(trail.state.history.state.steps[3].trailState.$timeRange?.state.from).toBe('now-15m'); + }); + + it('Should have time range `from` be set back to "now-6h"', () => { + expect(trail.state.$timeRange?.state.from).toBe('now-6h'); + }); + }); + }); + }); + }); + + function getFilterVar() { + const variable = sceneGraph.lookupVariable(VAR_FILTERS, trail); + if (variable instanceof AdHocFiltersVariable) { + return variable; + } + throw new Error('getFilterVar failed'); + } + + function getStepFilterVar(step: number) { + const variable = trail.state.history.state.steps[step].trailState.$variables?.getByName(VAR_FILTERS); + if (variable instanceof AdHocFiltersVariable) { + return variable; + } + throw new Error(`getStepFilterVar failed for step ${step}`); + } + + it('Should have default empty filter', () => { + expect(getFilterVar().state.filters.length).toBe(0); + }); + + describe('And when changing the filter to zone=a', () => { + beforeEach(() => { + getFilterVar().setState({ filters: [{ key: 'zone', operator: '=', value: 'a' }] }); + }); + + it('should sync state with url', () => { + expect(decodeURIComponent(locationService.getSearchObject()['var-filters']?.toString()!)).toBe('zone|=|a'); + }); + + it('should add history step', () => { + expect(trail.state.history.state.steps[2].type).toBe('filters'); + }); + + it('Should set history currentStep to 2', () => { + expect(trail.state.history.state.currentStep).toBe(2); + }); + + it('Should set history step 2 parentIndex to 1', () => { + expect(trail.state.history.state.steps[2].parentIndex).toBe(1); + }); + + it('Should have filter be updated to "zone=a"', () => { + expect(getFilterVar().state.filters[0].key).toBe('zone'); + expect(getFilterVar().state.filters[0].value).toBe('a'); + }); + + it('Previous history step should have empty filter', () => { + expect(getStepFilterVar(1).state.filters.length).toBe(0); + }); + + it('Current history step should have new filter zone=a', () => { + expect(getStepFilterVar(2).state.filters[0].key).toBe('zone'); + expect(getStepFilterVar(2).state.filters[0].value).toBe('a'); + }); + + describe('And when traversing back to step 1', () => { + beforeEach(() => { + trail.state.history.goBackToStep(1); + }); + + it('Should set history currentStep to 1', () => { + expect(trail.state.history.state.currentStep).toBe(1); + }); + + it('should sync state with url', () => { + expect(locationService.getSearchObject()['var-filters']).toBe(''); + }); + + it('Should have filters set back to empty', () => { + expect(getFilterVar().state.filters.length).toBe(0); + }); + + describe('And when changing the filter to zone=b', () => { + beforeEach(() => { + getFilterVar().setState({ filters: [{ key: 'zone', operator: '=', value: 'b' }] }); + }); + + it('should sync state with url', () => { + expect(decodeURIComponent(locationService.getSearchObject()['var-filters']?.toString()!)).toBe( + 'zone|=|b' + ); + }); + + it('should add history step', () => { + expect(trail.state.history.state.steps[3].type).toBe('filters'); + }); + + it('Should set history currentStep to 3', () => { + expect(trail.state.history.state.currentStep).toBe(3); + }); + + it('Should set history step 3 parentIndex to 1', () => { + expect(trail.state.history.state.steps[3].parentIndex).toBe(1); + }); + + it('Should have filter be updated to "zone=b"', () => { + expect(getFilterVar().state.filters[0].key).toBe('zone'); + expect(getFilterVar().state.filters[0].value).toBe('b'); + }); + + it('Parent history step 1 should still have empty filter', () => { + expect(getStepFilterVar(1).state.filters.length).toBe(0); + }); + + it('History step 2 should still have old filter zone=a', () => { + expect(getStepFilterVar(2).state.filters[0].key).toBe('zone'); + expect(getStepFilterVar(2).state.filters[0].value).toBe('a'); + }); + + it('Current history step 3 should have new filter zone=b', () => { + expect(getStepFilterVar(3).state.filters[0].key).toBe('zone'); + expect(getStepFilterVar(3).state.filters[0].value).toBe('b'); + }); + + describe('And then when returning again to step 1', () => { + beforeEach(() => { + trail.state.history.goBackToStep(1); + }); + + it('Should set history currentStep to 1', () => { + expect(trail.state.history.state.currentStep).toBe(1); + }); + + it('should sync state with url', () => { + expect(locationService.getSearchObject()['var-filters']).toBe(''); + }); + + it('Should have filters set back to empty', () => { + expect(getFilterVar().state.filters.length).toBe(0); + }); + + it('History step 1 should still have empty filter', () => { + expect(getStepFilterVar(1).state.filters.length).toBe(0); + }); + + it('History step 2 should still have old filter zone=a', () => { + expect(getStepFilterVar(2).state.filters[0].key).toBe('zone'); + expect(getStepFilterVar(2).state.filters[0].value).toBe('a'); + }); + + it('History step 3 should have new filter zone=b', () => { + expect(getStepFilterVar(3).state.filters[0].key).toBe('zone'); + expect(getStepFilterVar(3).state.filters[0].value).toBe('b'); + }); + }); + }); + }); + }); }); describe('When going back to history step 1', () => { diff --git a/public/app/features/trails/DataTrail.tsx b/public/app/features/trails/DataTrail.tsx index 73d75201d87..e2ac91a180e 100644 --- a/public/app/features/trails/DataTrail.tsx +++ b/public/app/features/trails/DataTrail.tsx @@ -18,6 +18,7 @@ import { SceneRefreshPicker, SceneTimePicker, SceneTimeRange, + sceneUtils, SceneVariable, SceneVariableSet, VariableDependencyConfig, @@ -26,7 +27,7 @@ import { import { useStyles2 } from '@grafana/ui'; import { DataTrailSettings } from './DataTrailSettings'; -import { DataTrailHistory, DataTrailHistoryStep } from './DataTrailsHistory'; +import { DataTrailHistory } from './DataTrailsHistory'; import { MetricScene } from './MetricScene'; import { MetricSelectScene } from './MetricSelectScene'; import { MetricsHeader } from './MetricsHeader'; @@ -81,59 +82,43 @@ export class DataTrail extends SceneObjectBase { // Some scene elements publish this this.subscribeToEvent(MetricSelectedEvent, this._handleMetricSelectedEvent.bind(this)); - // Pay attention to changes in history (i.e., changing the step) - this.state.history.subscribeToState((newState, oldState) => { - const oldNumberOfSteps = oldState.steps.length; - const newNumberOfSteps = newState.steps.length; - - const newStepWasAppended = newNumberOfSteps > oldNumberOfSteps; - - if (newStepWasAppended) { - // A new step is a significant change. Update the URL to match the new state. - this.syncTrailToUrl(); - // In order for the `useBookmarkState` to re-evaluate after a new step was made: - this.forceRender(); - // Do nothing else because the step state is already up to date -- it created a new step! - return; - } - - if (oldState.currentStep === newState.currentStep) { - // The same step was clicked on -- no need to change anything. - return; - } - - // History changed because a different node was selected - const step = newState.steps[newState.currentStep]; - - if (!step) { - return; - } - - this.goBackToStep(step); - }); - const filtersVariable = sceneGraph.lookupVariable(VAR_FILTERS, this); - const stateSubscription = - filtersVariable instanceof AdHocFiltersVariable && - filtersVariable?.subscribeToState((newState, prevState) => { - if (!this._addingFilterWithoutReportingInteraction) { - reportChangeInLabelFilters(newState.filters, prevState.filters); - } - }); + if (filtersVariable instanceof AdHocFiltersVariable) { + this._subs.add( + filtersVariable?.subscribeToState((newState, prevState) => { + if (!this._addingFilterWithoutReportingInteraction) { + reportChangeInLabelFilters(newState.filters, prevState.filters); + } + }) + ); + } + + this.enableUrlSync(); return () => { + this.disableUrlSync(); + if (!this.state.embedded) { getTrailStore().setRecentTrail(this); } - if (stateSubscription) { - stateSubscription?.unsubscribe(); - } }; } + private enableUrlSync() { + if (!this.state.embedded) { + getUrlSyncManager().initSync(this); + } + } + + private disableUrlSync() { + if (!this.state.embedded) { + getUrlSyncManager().cleanUp(this); + } + } + protected _variableDependency = new VariableDependencyConfig(this, { variableNames: [VAR_DATASOURCE], - onReferencedVariableValueChanged: async (variable: SceneVariable) => { + onReferencedVariableValueChanged: (variable: SceneVariable) => { const { name } = variable.state; if (name === VAR_DATASOURCE) { this.datasourceHelper.reset(); @@ -153,13 +138,13 @@ export class DataTrail extends SceneObjectBase { } this._addingFilterWithoutReportingInteraction = true; - variable.setState({ - filters: [...variable.state.filters, filter], - }); + + variable.setState({ filters: [...variable.state.filters, filter] }); + this._addingFilterWithoutReportingInteraction = false; } - private _addingFilterWithoutReportingInteraction = false; + private _addingFilterWithoutReportingInteraction = false; private datasourceHelper = new MetricDatasourceHelper(this); public getMetricMetadata(metric?: string) { @@ -170,25 +155,21 @@ export class DataTrail extends SceneObjectBase { return this.getMetricMetadata(this.state.metric); } - private goBackToStep(step: DataTrailHistoryStep) { - if (!step.trailState.metric) { - step.trailState.metric = undefined; - } + public restoreFromHistoryStep(state: DataTrailState) { + this.disableUrlSync(); - this.setState(step.trailState); - this.syncTrailToUrl(); - } - - private syncTrailToUrl() { - if (this.state.embedded) { - // Embedded trails should not be altering the URL - return; - } + this.setState( + sceneUtils.cloneSceneObjectState(state, { + history: this.state.history, + metric: !state.metric ? undefined : state.metric, + }) + ); const urlState = getUrlSyncManager().getUrlState(this); const fullUrl = urlUtil.renderUrl(locationService.getLocation().pathname, urlState); + locationService.replace(fullUrl); - locationService.replace(encodeURI(fullUrl)); + this.enableUrlSync(); } private _handleMetricSelectedEvent(evt: MetricSelectedEvent) { diff --git a/public/app/features/trails/DataTrailsHistory.tsx b/public/app/features/trails/DataTrailsHistory.tsx index b69be31ab89..5c08c590e4e 100644 --- a/public/app/features/trails/DataTrailsHistory.tsx +++ b/public/app/features/trails/DataTrailsHistory.tsx @@ -124,14 +124,17 @@ export class DataTrailHistory extends SceneObjectBase { return; } - this.stepTransitionInProgress = true; const step = this.state.steps[stepIndex]; const type = step.type === 'metric' && step.trailState.metric === undefined ? 'metric-clear' : step.type; + reportExploreMetrics('history_step_clicked', { type }); + this.stepTransitionInProgress = true; this.setState({ currentStep: stepIndex }); - // The URL will update + getTrailFor(this).restoreFromHistoryStep(step.trailState); + + // The URL will update this.stepTransitionInProgress = false; } From 0582e05f8f4a24056f1a24e42443476f5857453f Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Tue, 23 Apr 2024 17:19:48 +0100 Subject: [PATCH 092/222] Add PoC for different MSW approach --- ...rafanaAlertmanagerDeliveryWarning.test.tsx | 47 ++++--------------- .../app/features/alerting/unified/mockApi.ts | 6 ++- .../unified/mocks/server/configure.ts | 15 ++++++ .../alerting/unified/mocks/server/handlers.ts | 10 ++++ 4 files changed, 37 insertions(+), 41 deletions(-) create mode 100644 public/app/features/alerting/unified/mocks/server/configure.ts create mode 100644 public/app/features/alerting/unified/mocks/server/handlers.ts diff --git a/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.test.tsx b/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.test.tsx index 8eec117d321..90d33346d38 100644 --- a/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.test.tsx +++ b/public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.test.tsx @@ -1,39 +1,20 @@ import { render, screen, waitFor } from '@testing-library/react'; -import { setupServer } from 'msw/node'; import React from 'react'; import { Provider } from 'react-redux'; -import { setBackendSrv } from '@grafana/runtime'; -import { backendSrv } from 'app/core/services/backend_srv'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { setAlertmanagerChoices } from 'app/features/alerting/unified/mocks/server/configure'; import { configureStore } from 'app/store/configureStore'; import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/types'; -import { mockAlertmanagerChoiceResponse } from '../mocks/alertmanagerApi'; import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; import { GrafanaAlertmanagerDeliveryWarning } from './GrafanaAlertmanagerDeliveryWarning'; +setupMswServer(); describe('GrafanaAlertmanagerDeliveryWarning', () => { - const server = setupServer(); - - beforeAll(() => { - setBackendSrv(backendSrv); - server.listen({ onUnhandledRequest: 'error' }); - }); - - afterAll(() => { - server.close(); - }); - - beforeEach(() => { - server.resetHandlers(); - }); - it('Should not render when the datasource is not Grafana', () => { - mockAlertmanagerChoiceResponse(server, { - alertmanagersChoice: AlertmanagerChoice.External, - numExternalAlertmanagers: 0, - }); + setAlertmanagerChoices(AlertmanagerChoice.External, 0); const { container } = renderWithStore( @@ -43,10 +24,7 @@ describe('GrafanaAlertmanagerDeliveryWarning', () => { }); it('Should render warning when the datasource is Grafana and using external AM', async () => { - mockAlertmanagerChoiceResponse(server, { - alertmanagersChoice: AlertmanagerChoice.External, - numExternalAlertmanagers: 1, - }); + setAlertmanagerChoices(AlertmanagerChoice.External, 1); renderWithStore(); @@ -54,10 +32,7 @@ describe('GrafanaAlertmanagerDeliveryWarning', () => { }); it('Should render warning when the datasource is Grafana and using All AM', async () => { - mockAlertmanagerChoiceResponse(server, { - alertmanagersChoice: AlertmanagerChoice.All, - numExternalAlertmanagers: 1, - }); + setAlertmanagerChoices(AlertmanagerChoice.All, 1); renderWithStore(); @@ -65,10 +40,7 @@ describe('GrafanaAlertmanagerDeliveryWarning', () => { }); it('Should render no warning when choice is Internal', async () => { - mockAlertmanagerChoiceResponse(server, { - alertmanagersChoice: AlertmanagerChoice.Internal, - numExternalAlertmanagers: 1, - }); + setAlertmanagerChoices(AlertmanagerChoice.Internal, 1); const { container } = renderWithStore( @@ -80,10 +52,7 @@ describe('GrafanaAlertmanagerDeliveryWarning', () => { }); it('Should render no warning when choice is All but no active AM instances', async () => { - mockAlertmanagerChoiceResponse(server, { - alertmanagersChoice: AlertmanagerChoice.All, - numExternalAlertmanagers: 0, - }); + setAlertmanagerChoices(AlertmanagerChoice.All, 0); const { container } = renderWithStore( diff --git a/public/app/features/alerting/unified/mockApi.ts b/public/app/features/alerting/unified/mockApi.ts index fe4403d3f97..3cce73e348e 100644 --- a/public/app/features/alerting/unified/mockApi.ts +++ b/public/app/features/alerting/unified/mockApi.ts @@ -424,10 +424,10 @@ export function mockDashboardApi(server: SetupServer) { }; } +const server = setupServer(); + // Creates a MSW server and sets up beforeAll, afterAll and beforeEach handlers for it export function setupMswServer() { - const server = setupServer(); - beforeAll(() => { setBackendSrv(backendSrv); server.listen({ onUnhandledRequest: 'error' }); @@ -443,3 +443,5 @@ export function setupMswServer() { return server; } + +export default server; diff --git a/public/app/features/alerting/unified/mocks/server/configure.ts b/public/app/features/alerting/unified/mocks/server/configure.ts new file mode 100644 index 00000000000..27e36830616 --- /dev/null +++ b/public/app/features/alerting/unified/mocks/server/configure.ts @@ -0,0 +1,15 @@ +import server from 'app/features/alerting/unified/mockApi'; +import { alertmanagerChoiceHandler } from 'app/features/alerting/unified/mocks/server/handlers'; +import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; + +/** + * Makes the mock server respond in a way that matches the different behaviour associated with + * Alertmanager choices and the number of configured external alertmanagers + */ +export const setAlertmanagerChoices = (alertmanagersChoice: AlertmanagerChoice, numExternalAlertmanagers: number) => { + const response = { + alertmanagersChoice, + numExternalAlertmanagers, + }; + server.use(alertmanagerChoiceHandler(response)); +}; diff --git a/public/app/features/alerting/unified/mocks/server/handlers.ts b/public/app/features/alerting/unified/mocks/server/handlers.ts new file mode 100644 index 00000000000..9861af3aa07 --- /dev/null +++ b/public/app/features/alerting/unified/mocks/server/handlers.ts @@ -0,0 +1,10 @@ +/** + * Contains definitions for all handlers that are required for test rendering of components within Alerting + */ + +import { HttpResponse, http } from 'msw'; + +import { defaultAlertmanagerChoiceResponse } from 'app/features/alerting/unified/mocks/alertmanagerApi'; + +export const alertmanagerChoiceHandler = (response = defaultAlertmanagerChoiceResponse) => + http.get('/api/v1/ngalert', () => HttpResponse.json(response)); From 9e54c450d700e7073bd547254ee0ca0a76fdbd66 Mon Sep 17 00:00:00 2001 From: Javier Ruiz Date: Wed, 24 Apr 2024 17:24:47 +0200 Subject: [PATCH 093/222] Add onClick behaviour to links to new tooltips (#84974) * Add onClick behaviour to new tooltips * Prefer using DataLink component --- .../features/visualization/data-hover/ExemplarHoverView.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/public/app/features/visualization/data-hover/ExemplarHoverView.tsx b/public/app/features/visualization/data-hover/ExemplarHoverView.tsx index 39718bb536a..42157a961f8 100644 --- a/public/app/features/visualization/data-hover/ExemplarHoverView.tsx +++ b/public/app/features/visualization/data-hover/ExemplarHoverView.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2, LinkModel } from '@grafana/data'; -import { LinkButton, useStyles2 } from '@grafana/ui'; +import { DataLinkButton, useStyles2 } from '@grafana/ui'; import { VizTooltipRow } from '@grafana/ui/src/components/VizTooltip/VizTooltipRow'; import { renderValue } from 'app/plugins/panel/geomap/utils/uiUtils'; @@ -42,9 +42,7 @@ export const ExemplarHoverView = ({ displayValues, links, header = 'Exemplar' }: {links && links.length > 0 && (
{links.map((link, i) => ( - - {link.title} - + ))}
)} From 8028d1c3e1c5d3a1bf6abd42928ef18eb570261b Mon Sep 17 00:00:00 2001 From: Ieva Date: Wed, 24 Apr 2024 16:55:42 +0100 Subject: [PATCH 094/222] Chore: Update tests to use team membership hooks (#86846) * update tests to use team membership hooks * linting --- .../commands/conflict_user_command_test.go | 6 +- .../accesscontrol/database/database_test.go | 26 +-- .../resourcepermissions/store_bench_test.go | 12 +- pkg/services/team/teamimpl/store_test.go | 174 +++++++++++------- 4 files changed, 132 insertions(+), 86 deletions(-) diff --git a/pkg/cmd/grafana-cli/commands/conflict_user_command_test.go b/pkg/cmd/grafana-cli/commands/conflict_user_command_test.go index 00ee5325dae..a918a730386 100644 --- a/pkg/cmd/grafana-cli/commands/conflict_user_command_test.go +++ b/pkg/cmd/grafana-cli/commands/conflict_user_command_test.go @@ -705,14 +705,12 @@ func TestIntegrationMergeUser(t *testing.T) { return user.ErrUserNotFound } require.NoError(t, err) - return nil + // this is the user we want to update to another team + return teamimpl.AddOrUpdateTeamMemberHook(sess, 1, testOrgID, team1.ID, false, 0) }) if err != nil { t.Error(err) } - // this is the user we want to update to another team - err = teamSvc.AddTeamMember(context.Background(), 1, testOrgID, team1.ID, false, 0) - require.NoError(t, err) // get users conflictUsers, err := GetUsersWithConflictingEmailsOrLogins(&cli.Context{Context: context.Background()}, sqlStore) diff --git a/pkg/services/accesscontrol/database/database_test.go b/pkg/services/accesscontrol/database/database_test.go index 5f84c37387a..87ae850e01e 100644 --- a/pkg/services/accesscontrol/database/database_test.go +++ b/pkg/services/accesscontrol/database/database_test.go @@ -92,7 +92,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) { t.Run(tt.desc, func(t *testing.T) { store, permissionStore, sql, teamSvc, _ := setupTestEnv(t) - user, team := createUserAndTeam(t, sql, teamSvc, tt.orgID) + user, team := createUserAndTeam(t, store.sql, sql, teamSvc, tt.orgID) for _, id := range tt.userPermissions { _, err := permissionStore.SetUserResourcePermission(context.Background(), tt.orgID, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{ @@ -164,7 +164,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) { func TestAccessControlStore_DeleteUserPermissions(t *testing.T) { t.Run("expect permissions in all orgs to be deleted", func(t *testing.T) { store, permissionsStore, sql, teamSvc, _ := setupTestEnv(t) - user, _ := createUserAndTeam(t, sql, teamSvc, 1) + user, _ := createUserAndTeam(t, store.sql, sql, teamSvc, 1) // generate permissions in org 1 _, err := permissionsStore.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{ @@ -204,7 +204,7 @@ func TestAccessControlStore_DeleteUserPermissions(t *testing.T) { t.Run("expect permissions in org 1 to be deleted", func(t *testing.T) { store, permissionsStore, sql, teamSvc, _ := setupTestEnv(t) - user, _ := createUserAndTeam(t, sql, teamSvc, 1) + user, _ := createUserAndTeam(t, store.sql, sql, teamSvc, 1) // generate permissions in org 1 _, err := permissionsStore.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{ @@ -246,7 +246,7 @@ func TestAccessControlStore_DeleteUserPermissions(t *testing.T) { func TestAccessControlStore_DeleteTeamPermissions(t *testing.T) { t.Run("expect permissions related to team to be deleted", func(t *testing.T) { store, permissionsStore, sql, teamSvc, _ := setupTestEnv(t) - user, team := createUserAndTeam(t, sql, teamSvc, 1) + user, team := createUserAndTeam(t, store.sql, sql, teamSvc, 1) // grant permission to the team _, err := permissionsStore.SetTeamResourcePermission(context.Background(), 1, team.ID, rs.SetResourcePermissionCommand{ @@ -280,7 +280,7 @@ func TestAccessControlStore_DeleteTeamPermissions(t *testing.T) { }) t.Run("expect permissions not related to team to be kept", func(t *testing.T) { store, permissionsStore, sql, teamSvc, _ := setupTestEnv(t) - user, team := createUserAndTeam(t, sql, teamSvc, 1) + user, team := createUserAndTeam(t, store.sql, sql, teamSvc, 1) // grant permission to the team _, err := permissionsStore.SetTeamResourcePermission(context.Background(), 1, team.ID, rs.SetResourcePermissionCommand{ @@ -314,7 +314,7 @@ func TestAccessControlStore_DeleteTeamPermissions(t *testing.T) { }) } -func createUserAndTeam(t *testing.T, userSrv user.Service, teamSvc team.Service, orgID int64) (*user.User, team.Team) { +func createUserAndTeam(t *testing.T, store db.DB, userSrv user.Service, teamSvc team.Service, orgID int64) (*user.User, team.Team) { t.Helper() user, err := userSrv.Create(context.Background(), &user.CreateUserCommand{ @@ -326,7 +326,9 @@ func createUserAndTeam(t *testing.T, userSrv user.Service, teamSvc team.Service, team, err := teamSvc.CreateTeam("team", "", orgID) require.NoError(t, err) - err = teamSvc.AddTeamMember(context.Background(), user.ID, orgID, team.ID, false, dashboardaccess.PERMISSION_VIEW) + err = store.WithDbSession(context.Background(), func(sess *db.Session) error { + return teamimpl.AddOrUpdateTeamMemberHook(sess, user.ID, orgID, team.ID, false, dashboardaccess.PERMISSION_VIEW) + }) require.NoError(t, err) return user, team @@ -348,7 +350,7 @@ type dbUser struct { teamID int64 } -func createUsersAndTeams(t *testing.T, svcs helperServices, orgID int64, users []testUser) []dbUser { +func createUsersAndTeams(t *testing.T, store db.DB, svcs helperServices, orgID int64, users []testUser) []dbUser { t.Helper() res := []dbUser{} @@ -374,7 +376,9 @@ func createUsersAndTeams(t *testing.T, svcs helperServices, orgID int64, users [ team, err := svcs.teamSvc.CreateTeam(fmt.Sprintf("team%v", i+1), "", orgID) require.NoError(t, err) - err = svcs.teamSvc.AddTeamMember(context.Background(), user.ID, orgID, team.ID, false, dashboardaccess.PERMISSION_VIEW) + err = store.WithDbSession(context.Background(), func(sess *db.Session) error { + return teamimpl.AddOrUpdateTeamMemberHook(sess, user.ID, orgID, team.ID, false, dashboardaccess.PERMISSION_VIEW) + }) require.NoError(t, err) err = svcs.orgSvc.UpdateOrgUser(context.Background(), @@ -652,7 +656,7 @@ func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { acStore, permissionsStore, userSvc, teamSvc, orgSvc := setupTestEnv(t) - dbUsers := createUsersAndTeams(t, helperServices{userSvc, teamSvc, orgSvc}, 1, tt.users) + dbUsers := createUsersAndTeams(t, acStore.sql, helperServices{userSvc, teamSvc, orgSvc}, 1, tt.users) // Switch userID and TeamID by the real stored ones for i := range tt.permCmds { @@ -732,7 +736,7 @@ func TestAccessControlStore_GetUsersBasicRoles(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { acStore, _, userSvc, teamSvc, orgSvc := setupTestEnv(t) - dbUsers := createUsersAndTeams(t, helperServices{userSvc, teamSvc, orgSvc}, 1, tt.users) + dbUsers := createUsersAndTeams(t, acStore.sql, helperServices{userSvc, teamSvc, orgSvc}, 1, tt.users) // Test dbRoles, err := acStore.GetUsersBasicRoles(ctx, tt.userFilter, 1) diff --git a/pkg/services/accesscontrol/resourcepermissions/store_bench_test.go b/pkg/services/accesscontrol/resourcepermissions/store_bench_test.go index 2d0f5834a6e..3c0039da56f 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store_bench_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/store_bench_test.go @@ -138,15 +138,15 @@ func GenerateDatasourcePermissions(b *testing.B, db db.DB, cfg *setting.Cfg, ac return dataSources } -func generateTeamsAndUsers(b *testing.B, db db.DB, cfg *setting.Cfg, users int) ([]int64, []int64) { - teamSvc, err := teamimpl.ProvideService(db, cfg) +func generateTeamsAndUsers(b *testing.B, store db.DB, cfg *setting.Cfg, users int) ([]int64, []int64) { + teamSvc, err := teamimpl.ProvideService(store, cfg) require.NoError(b, err) numberOfTeams := int(math.Ceil(float64(users) / UsersPerTeam)) globalUserId := 0 qs := quotatest.New(false, nil) - orgSvc, err := orgimpl.ProvideService(db, cfg, qs) + orgSvc, err := orgimpl.ProvideService(store, cfg, qs) require.NoError(b, err) - usrSvc, err := userimpl.ProvideService(db, orgSvc, cfg, nil, nil, qs, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService(store, orgSvc, cfg, nil, nil, qs, supportbundlestest.NewFakeBundleService()) require.NoError(b, err) userIds := make([]int64, 0) teamIds := make([]int64, 0) @@ -171,7 +171,9 @@ func generateTeamsAndUsers(b *testing.B, db db.DB, cfg *setting.Cfg, users int) globalUserId++ userIds = append(userIds, userId) - err = teamSvc.AddTeamMember(context.Background(), userId, 1, teamId, false, 1) + err = store.WithDbSession(context.Background(), func(sess *db.Session) error { + return teamimpl.AddOrUpdateTeamMemberHook(sess, userId, 1, teamId, false, 1) + }) require.NoError(b, err) } } diff --git a/pkg/services/team/teamimpl/store_test.go b/pkg/services/team/teamimpl/store_test.go index 4da956f05d6..5f3010947bf 100644 --- a/pkg/services/team/teamimpl/store_test.go +++ b/pkg/services/team/teamimpl/store_test.go @@ -92,9 +92,13 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { require.Equal(t, team1.OrgID, testOrgID) require.EqualValues(t, team1.MemberCount, 0) - err = teamSvc.AddTeamMember(context.Background(), userIds[0], testOrgID, team1.ID, false, 0) - require.NoError(t, err) - err = teamSvc.AddTeamMember(context.Background(), userIds[1], testOrgID, team1.ID, true, 0) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + err := AddOrUpdateTeamMemberHook(sess, userIds[0], testOrgID, team1.ID, false, 0) + if err != nil { + return err + } + return AddOrUpdateTeamMemberHook(sess, userIds[1], testOrgID, team1.ID, true, 0) + }) require.NoError(t, err) q1 := &team.GetTeamMembersQuery{OrgID: testOrgID, TeamID: team1.ID, SignedInUser: testUser} @@ -152,7 +156,9 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { team1 := teamQueryResult.Teams[0] - err = teamSvc.AddTeamMember(context.Background(), userId, testOrgID, team1.ID, true, 0) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return AddOrUpdateTeamMemberHook(sess, userId, testOrgID, team1.ID, true, 0) + }) require.NoError(t, err) memberQuery := &team.GetTeamMembersQuery{OrgID: testOrgID, TeamID: team1.ID, External: true, SignedInUser: testUser} @@ -168,7 +174,9 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { t.Run("Should be able to update users in a team", func(t *testing.T) { userId := userIds[0] - err = teamSvc.AddTeamMember(context.Background(), userId, testOrgID, team1.ID, false, 0) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return AddOrUpdateTeamMemberHook(sess, userId, testOrgID, team1.ID, false, 0) + }) require.NoError(t, err) qBeforeUpdate := &team.GetTeamMembersQuery{OrgID: testOrgID, TeamID: team1.ID, SignedInUser: testUser} @@ -176,13 +184,9 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { require.NoError(t, err) require.EqualValues(t, qBeforeUpdateResult[0].Permission, 0) - err = teamSvc.UpdateTeamMember(context.Background(), &team.UpdateTeamMemberCommand{ - UserID: userId, - OrgID: testOrgID, - TeamID: team1.ID, - Permission: dashboardaccess.PERMISSION_ADMIN, + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return AddOrUpdateTeamMemberHook(sess, userId, testOrgID, team1.ID, false, dashboardaccess.PERMISSION_ADMIN) }) - require.NoError(t, err) qAfterUpdate := &team.GetTeamMembersQuery{OrgID: testOrgID, TeamID: team1.ID, SignedInUser: testUser} @@ -195,7 +199,10 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { sqlStore = db.InitTestDB(t) setup() userID := userIds[0] - err = teamSvc.AddTeamMember(context.Background(), userID, testOrgID, team1.ID, false, 0) + + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return AddOrUpdateTeamMemberHook(sess, userID, testOrgID, team1.ID, false, 0) + }) require.NoError(t, err) qBeforeUpdate := &team.GetTeamMembersQuery{OrgID: testOrgID, TeamID: team1.ID, SignedInUser: testUser} @@ -204,13 +211,9 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { require.EqualValues(t, qBeforeUpdateResult[0].Permission, 0) invalidPermissionLevel := dashboardaccess.PERMISSION_EDIT - err = teamSvc.UpdateTeamMember(context.Background(), &team.UpdateTeamMemberCommand{ - UserID: userID, - OrgID: testOrgID, - TeamID: team1.ID, - Permission: invalidPermissionLevel, + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return AddOrUpdateTeamMemberHook(sess, userID, testOrgID, team1.ID, false, invalidPermissionLevel) }) - require.NoError(t, err) qAfterUpdate := &team.GetTeamMembersQuery{OrgID: testOrgID, TeamID: team1.ID, SignedInUser: testUser} @@ -219,19 +222,6 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { require.EqualValues(t, qAfterUpdateResult[0].Permission, 0) }) - t.Run("Shouldn't be able to update a user not in the team.", func(t *testing.T) { - sqlStore = db.InitTestDB(t) - setup() - err = teamSvc.UpdateTeamMember(context.Background(), &team.UpdateTeamMemberCommand{ - UserID: 1, - OrgID: testOrgID, - TeamID: team1.ID, - Permission: dashboardaccess.PERMISSION_ADMIN, - }) - - require.Error(t, err, team.ErrTeamMemberNotFound) - }) - t.Run("Should be able to search for teams", func(t *testing.T) { query := &team.SearchTeamsQuery{OrgID: testOrgID, Query: "group", Page: 1, SignedInUser: testUser} queryResult, err := teamSvc.SearchTeams(context.Background(), query) @@ -251,11 +241,30 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { require.NoError(t, err) // Add a team member - err = teamSvc.AddTeamMember(context.Background(), userIds[0], testOrgID, team2.ID, false, 0) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + err := AddOrUpdateTeamMemberHook(sess, userIds[2], testOrgID, team1.ID, false, 0) + if err != nil { + return err + } + err = AddOrUpdateTeamMemberHook(sess, userIds[3], testOrgID, team1.ID, false, 0) + if err != nil { + return err + } + return AddOrUpdateTeamMemberHook(sess, userIds[2], testOrgID, team2.ID, false, 0) + }) require.NoError(t, err) defer func() { - err := teamSvc.RemoveTeamMember(context.Background(), - &team.RemoveTeamMemberCommand{OrgID: testOrgID, UserID: userIds[0], TeamID: team2.ID}) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + err := RemoveTeamMemberHook(sess, &team.RemoveTeamMemberCommand{OrgID: testOrgID, UserID: userIds[2], TeamID: team1.ID}) + if err != nil { + return err + } + err = RemoveTeamMemberHook(sess, &team.RemoveTeamMemberCommand{OrgID: testOrgID, UserID: userIds[3], TeamID: team1.ID}) + if err != nil { + return err + } + return RemoveTeamMemberHook(sess, &team.RemoveTeamMemberCommand{OrgID: testOrgID, UserID: userIds[2], TeamID: team2.ID}) + }) require.NoError(t, err) }() @@ -304,7 +313,9 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { sqlStore = db.InitTestDB(t) setup() groupId := team2.ID - err := teamSvc.AddTeamMember(context.Background(), userIds[0], testOrgID, groupId, false, 0) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return AddOrUpdateTeamMemberHook(sess, userIds[0], testOrgID, groupId, false, 0) + }) require.NoError(t, err) query := &team.GetTeamsByUserQuery{ @@ -323,10 +334,14 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { }) t.Run("Should be able to remove users from a group", func(t *testing.T) { - err = teamSvc.AddTeamMember(context.Background(), userIds[0], testOrgID, team1.ID, false, 0) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return AddOrUpdateTeamMemberHook(sess, userIds[0], testOrgID, team1.ID, false, 0) + }) require.NoError(t, err) - err = teamSvc.RemoveTeamMember(context.Background(), &team.RemoveTeamMemberCommand{OrgID: testOrgID, TeamID: team1.ID, UserID: userIds[0]}) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return RemoveTeamMemberHook(sess, &team.RemoveTeamMemberCommand{OrgID: testOrgID, TeamID: team1.ID, UserID: userIds[0]}) + }) require.NoError(t, err) q2 := &team.GetTeamMembersQuery{OrgID: testOrgID, TeamID: team1.ID, SignedInUser: testUser} @@ -336,16 +351,22 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { }) t.Run("Should have empty teams", func(t *testing.T) { - err = teamSvc.AddTeamMember(context.Background(), userIds[0], testOrgID, team1.ID, false, dashboardaccess.PERMISSION_ADMIN) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return AddOrUpdateTeamMemberHook(sess, userIds[0], testOrgID, team1.ID, false, dashboardaccess.PERMISSION_ADMIN) + }) require.NoError(t, err) t.Run("A user should be able to remove the admin permission for the last admin", func(t *testing.T) { - err = teamSvc.UpdateTeamMember(context.Background(), &team.UpdateTeamMemberCommand{OrgID: testOrgID, TeamID: team1.ID, UserID: userIds[0], Permission: 0}) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return AddOrUpdateTeamMemberHook(sess, userIds[0], testOrgID, team1.ID, false, 0) + }) require.NoError(t, err) }) t.Run("A user should be able to remove the last member", func(t *testing.T) { - err = teamSvc.RemoveTeamMember(context.Background(), &team.RemoveTeamMemberCommand{OrgID: testOrgID, TeamID: team1.ID, UserID: userIds[0]}) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return RemoveTeamMemberHook(sess, &team.RemoveTeamMemberCommand{OrgID: testOrgID, TeamID: team1.ID, UserID: userIds[0]}) + }) require.NoError(t, err) }) @@ -353,12 +374,17 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { sqlStore = db.InitTestDB(t) setup() - err = teamSvc.AddTeamMember(context.Background(), userIds[0], testOrgID, team1.ID, false, dashboardaccess.PERMISSION_ADMIN) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + err := AddOrUpdateTeamMemberHook(sess, userIds[0], testOrgID, team1.ID, false, dashboardaccess.PERMISSION_ADMIN) + if err != nil { + return err + } + return AddOrUpdateTeamMemberHook(sess, userIds[1], testOrgID, team1.ID, false, dashboardaccess.PERMISSION_ADMIN) + }) require.NoError(t, err) - - err = teamSvc.AddTeamMember(context.Background(), userIds[1], testOrgID, team1.ID, false, dashboardaccess.PERMISSION_ADMIN) - require.NoError(t, err) - err = teamSvc.UpdateTeamMember(context.Background(), &team.UpdateTeamMemberCommand{OrgID: testOrgID, TeamID: team1.ID, UserID: userIds[0], Permission: 0}) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + return AddOrUpdateTeamMemberHook(sess, userIds[0], testOrgID, team1.ID, false, 0) + }) require.NoError(t, err) }) }) @@ -379,12 +405,17 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { hiddenUsers := map[string]struct{}{"loginuser0": {}, "loginuser1": {}} teamId := team1.ID - err = teamSvc.AddTeamMember(context.Background(), userIds[0], testOrgID, teamId, false, 0) - require.NoError(t, err) - err = teamSvc.AddTeamMember(context.Background(), userIds[1], testOrgID, teamId, false, 0) - require.NoError(t, err) - err = teamSvc.AddTeamMember(context.Background(), userIds[2], testOrgID, teamId, false, 0) - require.NoError(t, err) + err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + err := AddOrUpdateTeamMemberHook(sess, userIds[0], testOrgID, teamId, false, 0) + if err != nil { + return err + } + err = AddOrUpdateTeamMemberHook(sess, userIds[1], testOrgID, teamId, false, 0) + if err != nil { + return err + } + return AddOrUpdateTeamMemberHook(sess, userIds[2], testOrgID, teamId, false, 0) + }) searchQuery := &team.SearchTeamsQuery{OrgID: testOrgID, Page: 1, Limit: 10, SignedInUser: signedInUser, HiddenUsers: hiddenUsers} searchQueryResult, err := teamSvc.SearchTeams(context.Background(), searchQuery) @@ -417,13 +448,16 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { require.NoError(t, err) groupId := team2.ID - // add service account to team - err = teamSvc.AddTeamMember(context.Background(), serviceAccount.ID, testOrgID, groupId, false, 0) - require.NoError(t, err) - - // add user to team - err = teamSvc.AddTeamMember(context.Background(), userIds[0], testOrgID, groupId, false, 0) - require.NoError(t, err) + dbErr := sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + // add service account to team + err := AddOrUpdateTeamMemberHook(sess, serviceAccount.ID, testOrgID, groupId, false, 0) + if err != nil { + return err + } + // add user to team + return AddOrUpdateTeamMemberHook(sess, userIds[0], testOrgID, groupId, false, 0) + }) + require.NoError(t, dbErr) teamMembersQuery := &team.GetTeamMembersQuery{ OrgID: testOrgID, @@ -550,14 +584,22 @@ func TestIntegrationSQLStore_GetTeamMembers_ACFilter(t *testing.T) { userIds[i] = user.ID } - errAddMember := teamSvc.AddTeamMember(context.Background(), userIds[0], testOrgID, team1.ID, false, 0) - require.NoError(t, errAddMember) - errAddMember = teamSvc.AddTeamMember(context.Background(), userIds[1], testOrgID, team1.ID, false, 0) - require.NoError(t, errAddMember) - errAddMember = teamSvc.AddTeamMember(context.Background(), userIds[2], testOrgID, team2.ID, false, 0) - require.NoError(t, errAddMember) - errAddMember = teamSvc.AddTeamMember(context.Background(), userIds[3], testOrgID, team2.ID, false, 0) - require.NoError(t, errAddMember) + errAddMembers := store.WithDbSession(context.Background(), func(sess *db.Session) error { + err := AddOrUpdateTeamMemberHook(sess, userIds[0], testOrgID, team1.ID, false, 0) + if err != nil { + return err + } + err = AddOrUpdateTeamMemberHook(sess, userIds[1], testOrgID, team1.ID, false, 0) + if err != nil { + return err + } + err = AddOrUpdateTeamMemberHook(sess, userIds[2], testOrgID, team2.ID, false, 0) + if err != nil { + return err + } + return AddOrUpdateTeamMemberHook(sess, userIds[3], testOrgID, team2.ID, false, 0) + }) + require.NoError(t, errAddMembers) } store, cfg := db.InitTestDBWithCfg(t, db.InitTestDBOpt{}) From 53ead9904d0b51f648c2da0f75c1c6c414bde792 Mon Sep 17 00:00:00 2001 From: Darren Janeczek <38694490+darrenjaneczek@users.noreply.github.com> Date: Wed, 24 Apr 2024 12:05:48 -0400 Subject: [PATCH 095/222] datatrails: interpolate adhoc variables and datasource variables when opening "explore metrics" from dashboard panels (#86252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: interpolate "explore metrics" from panels --------- Co-authored-by: Darren Janeczek * fix: remove support for legacy dashboard - simplify code - take advantage of scenes dashboard async and datasource api object --------- Co-authored-by: Torkel Ödegaard --- .../scene/PanelMenuBehavior.tsx | 2 +- .../features/dashboard/utils/getPanelMenu.ts | 5 - .../Integrations/dashboardIntegration.ts | 91 +++++++++---------- .../app/features/trails/Integrations/utils.ts | 29 +----- 4 files changed, 45 insertions(+), 82 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index 26d726f3d22..f499bd752a9 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -168,7 +168,7 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) { } if (config.featureToggles.exploreMetrics) { - addDataTrailPanelAction(dashboard, panel, items); + await addDataTrailPanelAction(dashboard, panel, items); } if (exploreMenuItem) { diff --git a/public/app/features/dashboard/utils/getPanelMenu.ts b/public/app/features/dashboard/utils/getPanelMenu.ts index 6dda0cc8b93..f1deeff64db 100644 --- a/public/app/features/dashboard/utils/getPanelMenu.ts +++ b/public/app/features/dashboard/utils/getPanelMenu.ts @@ -25,7 +25,6 @@ import { DashboardInteractions } from 'app/features/dashboard-scene/utils/intera import { InspectTab } from 'app/features/inspector/types'; import { isPanelModelLibraryPanel } from 'app/features/library-panels/guard'; import { createExtensionSubMenu } from 'app/features/plugins/extensions/utils'; -import { addDataTrailPanelAction } from 'app/features/trails/Integrations/dashboardIntegration'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; import { dispatch, store } from 'app/store/store'; @@ -164,10 +163,6 @@ export function getPanelMenu( }); } - if (config.featureToggles.exploreMetrics) { - addDataTrailPanelAction(dashboard, panel, menu); - } - const inspectMenu: PanelMenuItem[] = []; // Only show these inspect actions for data plugins diff --git a/public/app/features/trails/Integrations/dashboardIntegration.ts b/public/app/features/trails/Integrations/dashboardIntegration.ts index 82b2c437b57..12fa49cc771 100644 --- a/public/app/features/trails/Integrations/dashboardIntegration.ts +++ b/public/app/features/trails/Integrations/dashboardIntegration.ts @@ -1,55 +1,55 @@ -import { isString } from 'lodash'; - -import { PanelMenuItem, PanelModel } from '@grafana/data'; +import { PanelMenuItem } from '@grafana/data'; +import { PromQuery } from '@grafana/prometheus'; import { getDataSourceSrv } from '@grafana/runtime'; import { SceneTimeRangeLike, VizPanel } from '@grafana/scenes'; -import { DataSourceRef } from '@grafana/schema'; +import { DataQuery, DataSourceRef } from '@grafana/schema'; +import { getQueryRunnerFor } from 'app/features/dashboard-scene/utils/utils'; -import { DashboardModel } from '../../dashboard/state'; import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene'; import { MetricScene } from '../MetricScene'; import { reportExploreMetrics } from '../interactions'; import { DataTrailEmbedded, DataTrailEmbeddedState } from './DataTrailEmbedded'; -import { SceneDrawerAsScene, launchSceneDrawerInGlobalModal } from './SceneDrawer'; +import { SceneDrawerAsScene } from './SceneDrawer'; import { QueryMetric, getQueryMetrics } from './getQueryMetrics'; -import { - createAdHocFilters, - getPanelType, - getQueryMetricLabel, - getQueryRunner, - getTimeRangeFromDashboard, -} from './utils'; +import { createAdHocFilters, getQueryMetricLabel, getTimeRangeFromDashboard } from './utils'; -export function addDataTrailPanelAction( - dashboard: DashboardScene | DashboardModel, - panel: VizPanel | PanelModel, - items: PanelMenuItem[] -) { - const panelType = getPanelType(panel); - if (panelType !== 'timeseries') { +export async function addDataTrailPanelAction(dashboard: DashboardScene, panel: VizPanel, items: PanelMenuItem[]) { + if (panel.state.pluginId !== 'timeseries') { return; } - const queryRunner = getQueryRunner(panel); - if (!queryRunner) { + const queryRunner = getQueryRunnerFor(panel); + if (queryRunner == null) { return; } - const ds = getDataSourceSrv().getInstanceSettings(queryRunner.state.datasource); + const { queries, datasource, data } = queryRunner.state; - if (ds?.meta.id !== 'prometheus') { + if (datasource == null) { return; } - const queries = queryRunner.state.queries.map((q) => q.expr).filter(isString); + if (datasource.type !== 'prometheus') { + return; + } - const queryMetrics = getQueryMetrics(queries); + const dataSourceApi = await getDataSourceSrv().get(datasource); + + if (dataSourceApi.interpolateVariablesInQueries == null) { + return; + } + + const interpolated = dataSourceApi + .interpolateVariablesInQueries(queries, { __sceneObject: { value: panel } }, data?.request?.filters) + .filter(isPromQuery); + + const queryMetrics = getQueryMetrics(interpolated.map((q) => q.expr)); const subMenu: PanelMenuItem[] = queryMetrics.map((item) => { return { text: getQueryMetricLabel(item), - onClick: createClickHandler(item, dashboard, ds), + onClick: createClickHandler(item, dashboard, dataSourceApi), }; }); @@ -88,11 +88,7 @@ function getEmbeddedTrailsState( return state; } -function createCommonEmbeddedTrailStateProps( - item: QueryMetric, - dashboard: DashboardScene | DashboardModel, - ds: DataSourceRef -) { +function createCommonEmbeddedTrailStateProps(item: QueryMetric, dashboard: DashboardScene, ds: DataSourceRef) { const timeRange = getTimeRangeFromDashboard(dashboard); const trailState = getEmbeddedTrailsState(item, timeRange, ds.uid); const embeddedTrail: DataTrailEmbedded = new DataTrailEmbedded(trailState); @@ -111,21 +107,18 @@ function createCommonEmbeddedTrailStateProps( return commonProps; } -function createClickHandler(item: QueryMetric, dashboard: DashboardScene | DashboardModel, ds: DataSourceRef) { - if (dashboard instanceof DashboardScene) { - return () => { - const commonProps = createCommonEmbeddedTrailStateProps(item, dashboard, ds); - const drawerScene = new SceneDrawerAsScene({ - ...commonProps, - onDismiss: () => dashboard.closeModal(), - }); - reportExploreMetrics('exploration_started', { cause: 'dashboard_panel' }); - dashboard.showModal(drawerScene); - }; - } else { - return () => { - reportExploreMetrics('exploration_started', { cause: 'dashboard_panel' }); - launchSceneDrawerInGlobalModal(createCommonEmbeddedTrailStateProps(item, dashboard, ds)); - }; - } +function createClickHandler(item: QueryMetric, dashboard: DashboardScene, ds: DataSourceRef) { + return () => { + const commonProps = createCommonEmbeddedTrailStateProps(item, dashboard, ds); + const drawerScene = new SceneDrawerAsScene({ + ...commonProps, + onDismiss: () => dashboard.closeModal(), + }); + reportExploreMetrics('exploration_started', { cause: 'dashboard_panel' }); + dashboard.showModal(drawerScene); + }; +} + +export function isPromQuery(model: DataQuery): model is PromQuery { + return 'expr' in model; } diff --git a/public/app/features/trails/Integrations/utils.ts b/public/app/features/trails/Integrations/utils.ts index f5faf434c02..372d81ae999 100644 --- a/public/app/features/trails/Integrations/utils.ts +++ b/public/app/features/trails/Integrations/utils.ts @@ -1,9 +1,5 @@ -import { PanelModel } from '@grafana/data'; import { QueryBuilderLabelFilter } from '@grafana/prometheus/src/querybuilder/shared/types'; -import { SceneQueryRunner, SceneTimeRange, VizPanel } from '@grafana/scenes'; -import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; -import { getQueryRunnerFor } from 'app/features/dashboard-scene/utils/utils'; import { QueryMetric } from './getQueryMetrics'; @@ -12,22 +8,8 @@ export function isEquals(labelFilter: QueryBuilderLabelFilter) { return labelFilter.op === '='; } -export function getQueryRunner(panel: VizPanel | PanelModel) { - if (panel instanceof VizPanel) { - return getQueryRunnerFor(panel); - } - - return new SceneQueryRunner({ datasource: panel.datasource || undefined, queries: panel.targets || [] }); -} - -export function getTimeRangeFromDashboard(dashboard: DashboardScene | DashboardModel) { - if (dashboard instanceof DashboardScene) { - return dashboard.state.$timeRange!.clone(); - } - if (dashboard instanceof DashboardModel) { - return new SceneTimeRange({ ...dashboard.time }); - } - return new SceneTimeRange(); +export function getTimeRangeFromDashboard(dashboard: DashboardScene) { + return dashboard.state.$timeRange!.clone(); } export function getQueryMetricLabel({ metric, labelFilters }: QueryMetric) { @@ -43,10 +25,3 @@ export function getQueryMetricLabel({ metric, labelFilters }: QueryMetric) { export function createAdHocFilters(labels: QueryBuilderLabelFilter[]) { return labels?.map((label) => ({ key: label.label, value: label.value, operator: label.op })); } - -export function getPanelType(panel: VizPanel | PanelModel) { - if (panel instanceof VizPanel) { - return panel.state.pluginId; - } - return panel.type; -} From 0e81fdffbe0f01c333335917b3f2d11893afdc58 Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Wed, 24 Apr 2024 12:40:27 -0400 Subject: [PATCH 096/222] Docs: add tooltips shared content (#86553) * Replaced shared tooltips file with text in xy chart * Added tooltip info for heatmap * Updated tooltip shared files, renamed one file, updated configure tooltips * updated tooltip shared file paths * Added tooltips shared files in relevant visualizations * Added where shared file is used in tooltip-options-1 * Added where shared file is used in tooltip-options-2 * Added intro text and justifications to shared files * Removed tooltips intro text from visualizations with shared files * Added names of files to comments in shared files --- .../configure-tooltips/index.md | 4 +++ .../visualizations/bar-chart/index.md | 10 ++++-- .../visualizations/candlestick/index.md | 4 +++ .../visualizations/heatmap/index.md | 22 +++++++++++-- .../visualizations/pie-chart/index.md | 4 ++- .../visualizations/state-timeline/index.md | 4 +++ .../visualizations/status-history/index.md | 4 +++ .../visualizations/time-series/index.md | 12 ++----- .../visualizations/trend/index.md | 4 +++ .../visualizations/xy-chart/index.md | 15 +++++++-- .../shared/visualizations/tooltip-mode.md | 13 -------- .../visualizations/tooltip-options-1.md | 31 +++++++++++++++++++ .../visualizations/tooltip-options-2.md | 21 +++++++++++-- 13 files changed, 116 insertions(+), 32 deletions(-) delete mode 100644 docs/sources/shared/visualizations/tooltip-mode.md create mode 100644 docs/sources/shared/visualizations/tooltip-options-1.md diff --git a/docs/sources/panels-visualizations/configure-tooltips/index.md b/docs/sources/panels-visualizations/configure-tooltips/index.md index 0c486671e99..4b2956e7dc9 100644 --- a/docs/sources/panels-visualizations/configure-tooltips/index.md +++ b/docs/sources/panels-visualizations/configure-tooltips/index.md @@ -67,6 +67,10 @@ Set the hover proximity (in pixels) to control how close the cursor must be to a ![Adding a hover proximity limit for tooltips](/media/docs/grafana/gif-grafana-10-4-hover-proximity.gif) +### Max height + +Set the maximum height of the tooltip box. The default is 600 pixels. + ### Show histogram (Y axis) For the heatmap visualization only, when you set the **Tooltip mode** to **Single**, the **Show histogram (Y axis)** option is displayed. This option controls whether or not the tooltip includes a histogram representing the y-axis. diff --git a/docs/sources/panels-visualizations/visualizations/bar-chart/index.md b/docs/sources/panels-visualizations/visualizations/bar-chart/index.md index 918cae3a833..41536af170b 100644 --- a/docs/sources/panels-visualizations/visualizations/bar-chart/index.md +++ b/docs/sources/panels-visualizations/visualizations/bar-chart/index.md @@ -129,7 +129,13 @@ Transparency of the gradient is calculated based on the values on the y-axis. Op Gradient color is generated based on the hue of the line color. -{{< docs/shared lookup="visualizations/tooltip-mode.md" source="grafana" version="" >}} +## Tooltip options + +{{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="" >}} + +## Legend options + +Legend options control the series names and statistics that appear under or to the right of the graph. {{< docs/shared lookup="visualizations/legend-mode.md" source="grafana" version="" >}} @@ -191,7 +197,7 @@ Set a **Soft min** or **soft max** option for better control of Y-axis limits. B You can set standard min/max options to define hard limits of the Y-axis. For more information, refer to [Standard options definitions][]. -{{< docs/shared lookup="visualizations/multiple-y-axes.md" source="grafana" version="" leveloffset="+2" >}} +{{< docs/shared lookup="visualizations/multiple-y-axes.md" source="grafana" version="" leveloffset="+2" >}} {{% docs/reference %}} [Add a field override]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-overrides#add-a-field-override" diff --git a/docs/sources/panels-visualizations/visualizations/candlestick/index.md b/docs/sources/panels-visualizations/visualizations/candlestick/index.md index 29714790a88..715f20a246c 100644 --- a/docs/sources/panels-visualizations/visualizations/candlestick/index.md +++ b/docs/sources/panels-visualizations/visualizations/candlestick/index.md @@ -114,6 +114,10 @@ If your data can't be mapped to these dimensions for some reason (for example, b The candlestick visualization is based on the time series visualization. It can visualize additional data dimensions beyond open, high, low, close, and volume The **Include** and **Ignore** options allow it to visualize other included data such as simple moving averages, Bollinger bands and more, using the same styles and configurations available in the [time series][time series visualization] visualization. +## Tooltip options + +{{< docs/shared lookup="visualizations/tooltip-options-2.md" source="grafana" version="" >}} + {{% docs/reference %}} [time series visualization]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/visualizations/time-series" [time series visualization]: "/docs/grafana-cloud/ -> /docs/grafana//panels-visualizations/visualizations/time-series" diff --git a/docs/sources/panels-visualizations/visualizations/heatmap/index.md b/docs/sources/panels-visualizations/visualizations/heatmap/index.md index 70efed729ca..18c757800e4 100644 --- a/docs/sources/panels-visualizations/visualizations/heatmap/index.md +++ b/docs/sources/panels-visualizations/visualizations/heatmap/index.md @@ -150,9 +150,25 @@ Use these settings to refine your visualization. ### Tooltip -- **Show tooltip -** Show heatmap tooltip. -- **Show Histogram -** Show a Y-axis histogram on the tooltip. A histogram represents the distribution of the bucket values for a specific timestamp. -- **Show color scale -** Show a color scale on the tooltip. The color scale represents the mapping between bucket value and color. +#### Tooltip mode + +When you hover your cursor over the visualization, Grafana can display tooltips. Choose how tooltips behave. + +- **Single -** The hover tooltip shows only a single series, the one that you are hovering over on the visualization. +- **All -** The hover tooltip shows all series in the visualization. Grafana highlights the series that you are hovering over in bold in the series list in the tooltip. +- **Hidden -** Do not display the tooltip when you interact with the visualization. + +Use an override to hide individual series from the tooltip. + +#### Show histogram (Y axis) + +When you set the **Tooltip mode** to **Single**, this option is displayed. This option controls whether or not the tooltip includes a histogram representing the y-axis. + +#### Show color scale + +When you set the **Tooltip mode** to **Single**, this option is displayed. This option controls whether or not the tooltip includes the color scale that's also represented in the legend. When the color scale is included in the tooltip, it shows the hovered value on the scale: + +![Heatmap with a tooltip displayed showing the hovered value reflected in the color scale](/media/docs/grafana/panels-visualizations/screenshot-heatmap-tooltip-color-scale-v11.0.png) ### Legend diff --git a/docs/sources/panels-visualizations/visualizations/pie-chart/index.md b/docs/sources/panels-visualizations/visualizations/pie-chart/index.md index ffb8f272911..20a6c3d63ac 100644 --- a/docs/sources/panels-visualizations/visualizations/pie-chart/index.md +++ b/docs/sources/panels-visualizations/visualizations/pie-chart/index.md @@ -80,7 +80,9 @@ The following example shows a pie chart with **Name** and **Percent** labels dis ![Pie chart labels](/static/img/docs/pie-chart-panel/pie-chart-labels-7-5.png) -{{< docs/shared lookup="visualizations/tooltip-mode.md" source="grafana" version="" >}} +## Tooltip options + +{{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="" >}} ## Legend options diff --git a/docs/sources/panels-visualizations/visualizations/state-timeline/index.md b/docs/sources/panels-visualizations/visualizations/state-timeline/index.md index ff8ede7aeb5..6f47fa94a2f 100644 --- a/docs/sources/panels-visualizations/visualizations/state-timeline/index.md +++ b/docs/sources/panels-visualizations/visualizations/state-timeline/index.md @@ -136,6 +136,10 @@ When the legend option is enabled it can show either the value mappings or the t {{< docs/shared lookup="visualizations/legend-mode.md" source="grafana" version="" >}} +## Tooltip options + +{{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="" >}} + {{% docs/reference %}} [Color scheme]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-standard-options#color-scheme" [Color scheme]: "/docs/grafana-cloud/ -> /docs/grafana//panels-visualizations/configure-standard-options#color-scheme" diff --git a/docs/sources/panels-visualizations/visualizations/status-history/index.md b/docs/sources/panels-visualizations/visualizations/status-history/index.md index e73cf0b3281..12ad71fd1c3 100644 --- a/docs/sources/panels-visualizations/visualizations/status-history/index.md +++ b/docs/sources/panels-visualizations/visualizations/status-history/index.md @@ -116,6 +116,10 @@ When the legend option is enabled it can show either the value mappings or the t {{< docs/shared lookup="visualizations/legend-mode.md" source="grafana" version="" >}} +## Tooltip options + +{{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="" >}} + {{% docs/reference %}} [Value mappings]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-value-mappings" [Value mappings]: "/docs/grafana-cloud/ -> /docs/grafana//panels-visualizations/configure-value-mappings" diff --git a/docs/sources/panels-visualizations/visualizations/time-series/index.md b/docs/sources/panels-visualizations/visualizations/time-series/index.md index 1846d877077..67d81494027 100644 --- a/docs/sources/panels-visualizations/visualizations/time-series/index.md +++ b/docs/sources/panels-visualizations/visualizations/time-series/index.md @@ -52,19 +52,13 @@ The following video guides you through the creation steps and common customizati ## Tooltip options -Tooltip options control the information overlay that appears when you hover over data points in the graph. - -{{< docs/shared lookup="visualizations/tooltip-mode.md" source="grafana" version="" >}} - -### Hover proximity - -This option controls how close your cursor must be to a data point before the tooltip appears. Values are in pixels. +{{< docs/shared lookup="visualizations/tooltip-options-2.md" source="grafana" version="" >}} ## Legend options -Legend options control the series names and statistics that appear under or to the right of the graph. +Legend options control the series names and statistics that appear under or to the right of the visualization. -{{< docs/shared lookup="visualizations/legend-mode.md" source="grafana" version="" >}} +{{< docs/shared lookup="visualizations/legend-mode.md" source="grafana" version="" >}} ## Graph styles diff --git a/docs/sources/panels-visualizations/visualizations/trend/index.md b/docs/sources/panels-visualizations/visualizations/trend/index.md index c7ae5cef5e4..823c20745a6 100644 --- a/docs/sources/panels-visualizations/visualizations/trend/index.md +++ b/docs/sources/panels-visualizations/visualizations/trend/index.md @@ -38,6 +38,10 @@ For example, you could represent engine power and torque versus speed where spee {{< figure src="/media/docs/grafana/screenshot-grafana-10-0-trend-panel-new-colors.png" max-width="750px" caption="Trend engine power and torque curves" >}} +## Tooltip options + +{{< docs/shared lookup="visualizations/tooltip-options-2.md" source="grafana" version="" >}} + {{% docs/reference %}} [Time series visualization]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/visualizations/time-series" [Time series visualization]: "/docs/grafana-cloud/ -> /docs/grafana//panels-visualizations/visualizations/time-series" diff --git a/docs/sources/panels-visualizations/visualizations/xy-chart/index.md b/docs/sources/panels-visualizations/visualizations/xy-chart/index.md index fcf479b34a2..141e83966f7 100644 --- a/docs/sources/panels-visualizations/visualizations/xy-chart/index.md +++ b/docs/sources/panels-visualizations/visualizations/xy-chart/index.md @@ -132,9 +132,20 @@ Set the width of the lines in pixels. ## Tooltip options -Tooltip options control the information overlay that appears when you hover over data points in the graph. +Tooltip options control the information overlay that appears when you hover over data points in the visualization. -{{< docs/shared lookup="visualizations/tooltip-options-2.md" source="grafana" version="" >}} +### Tooltip mode + +When you hover your cursor over the visualization, Grafana can display tooltips. Choose how tooltips behave. + +- **Single -** The hover tooltip shows only a single series, the one that you are hovering over on the visualization. +- **Hidden -** Do not display the tooltip when you interact with the visualization. + +Use an override to hide individual series from the tooltip. + +### Max height + +Set the maximum height of the tooltip box. The default is 600 pixels. ## Legend options diff --git a/docs/sources/shared/visualizations/tooltip-mode.md b/docs/sources/shared/visualizations/tooltip-mode.md deleted file mode 100644 index 75c2c4d2358..00000000000 --- a/docs/sources/shared/visualizations/tooltip-mode.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Tooltip mode ---- - -### Tooltip mode - -When you hover your cursor over the visualization, Grafana can display tooltips. Choose how tooltips behave. - -- **Single -** The hover tooltip shows only a single series, the one that you are hovering over on the visualization. -- **All -** The hover tooltip shows all series in the visualization. Grafana highlights the series that you are hovering over in bold in the series list in the tooltip. -- **Hidden -** Do not display the tooltip when you interact with the visualization. - -Use an override to hide individual series from the tooltip. diff --git a/docs/sources/shared/visualizations/tooltip-options-1.md b/docs/sources/shared/visualizations/tooltip-options-1.md new file mode 100644 index 00000000000..1ff0a90c821 --- /dev/null +++ b/docs/sources/shared/visualizations/tooltip-options-1.md @@ -0,0 +1,31 @@ +--- +title: Tooltip mode +comments: | + There are two tooltip shared files, tooltip-options-1.md and tooltip-options-2.md, to cover the most common combinations of options. + Using two shared files ensures that content remains consistent across visualizations that share the same options and users don't have to figure out which options apply to a specific visualization when reading that content. + This file is used in the following visualizations: bar chart, pie chart, state timeline, status history +--- + +Tooltip options control the information overlay that appears when you hover over data points in the visualization. + +### Tooltip mode + +When you hover your cursor over the visualization, Grafana can display tooltips. Choose how tooltips behave. + +- **Single -** The hover tooltip shows only a single series, the one that you are hovering over on the visualization. +- **All -** The hover tooltip shows all series in the visualization. Grafana highlights the series that you are hovering over in bold in the series list in the tooltip. +- **Hidden -** Do not display the tooltip when you interact with the visualization. + +Use an override to hide individual series from the tooltip. + +### Values sort order + +When you set the **Tooltip mode** to **All**, the **Values sort order** option is displayed. This option controls the order in which values are listed in a tooltip. Choose from the following: + +- **None** - Grafana automatically sorts the values displayed in a tooltip. +- **Ascending** - Values in the tooltip are listed from smallest to largest. +- **Descending** - Values in the tooltip are listed from largest to smallest. + +### Max height + +Set the maximum height of the tooltip box. The default is 600 pixels. diff --git a/docs/sources/shared/visualizations/tooltip-options-2.md b/docs/sources/shared/visualizations/tooltip-options-2.md index 9bb487fb9c5..2a5d35e2bd0 100644 --- a/docs/sources/shared/visualizations/tooltip-options-2.md +++ b/docs/sources/shared/visualizations/tooltip-options-2.md @@ -1,19 +1,36 @@ --- title: Tooltip options +comments: | + There are two tooltip shared files, tooltip-options-1.md and tooltip-options-2.md, to cover the most common combinations of options. + Using two shared files ensures that content remains consistent across visualizations that share the same options and users don't have to figure out which options apply to a specific visualization when reading that content. + This file is used in the following visualizations: candlestick, time series, trend --- +Tooltip options control the information overlay that appears when you hover over data points in the visualization. + ### Tooltip mode When you hover your cursor over the visualization, Grafana can display tooltips. Choose how tooltips behave. - **Single -** The hover tooltip shows only a single series, the one that you are hovering over on the visualization. +- **All -** The hover tooltip shows all series in the visualization. Grafana highlights the series that you are hovering over in bold in the series list in the tooltip. - **Hidden -** Do not display the tooltip when you interact with the visualization. Use an override to hide individual series from the tooltip. -### Max width +### Values sort order -Set the maximum width of the tooltip box. The default is 300 pixels. +When you set the **Tooltip mode** to **All**, the **Values sort order** option is displayed. This option controls the order in which values are listed in a tooltip. Choose from the following: + +- **None** - Grafana automatically sorts the values displayed in a tooltip. +- **Ascending** - Values in the tooltip are listed from smallest to largest. +- **Descending** - Values in the tooltip are listed from largest to smallest. + +### Hover proximity + +Set the hover proximity (in pixels) to control how close the cursor must be to a data point to trigger the tooltip to display. + +![Adding a hover proximity limit for tooltips](/media/docs/grafana/gif-grafana-10-4-hover-proximity.gif) ### Max height From f1aa6549f636ffff51cbe7f354a2a6fd823f87a2 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Wed, 24 Apr 2024 14:26:14 -0400 Subject: [PATCH 097/222] Chore: Upgrade go version to 1.22.2 (#86873) * Chore: Upgrade go version to 1.22.2 * upgrade to latest swagger for go 1.22 compatibility * regen openapi spec * upgrade go in github workflows --- .bingo/Variables.mk | 10 +- .bingo/swagger.mod | 2 +- .bingo/swagger.sum | 104 ++++++++++ .bingo/variables.env | 4 +- .drone.yml | 208 ++++++++++---------- .github/workflows/alerting-swagger-gen.yml | 2 +- .github/workflows/bump-version.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/pr-codeql-analysis-go.yml | 2 +- .github/workflows/pr-go-workspace-check.yml | 2 +- .github/workflows/publish-kinds-next.yml | 2 +- .github/workflows/publish-kinds-release.yml | 2 +- .github/workflows/verify-kinds.yml | 2 +- Dockerfile | 2 +- Makefile | 3 +- public/api-merged.json | 54 +++-- public/openapi3.json | 54 +++-- scripts/drone/variables.star | 2 +- 18 files changed, 295 insertions(+), 164 deletions(-) diff --git a/.bingo/Variables.mk b/.bingo/Variables.mk index c6a5d32a428..0a476ee23a0 100644 --- a/.bingo/Variables.mk +++ b/.bingo/Variables.mk @@ -1,4 +1,4 @@ -# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.8. DO NOT EDIT. +# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT. # All tools are designed to be build inside $GOBIN. BINGO_DIR := $(dir $(lastword $(MAKEFILE_LIST))) GOPATH ?= $(shell go env GOPATH) @@ -53,9 +53,11 @@ $(LEFTHOOK): $(BINGO_DIR)/lefthook.mod @echo "(re)installing $(GOBIN)/lefthook-v1.4.8" @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=lefthook.mod -o=$(GOBIN)/lefthook-v1.4.8 "github.com/evilmartians/lefthook" -SWAGGER := $(GOBIN)/swagger-v0.30.2 +# swagger 0.30.5 isn't compatibile with go 1.22 yet so pinning to a specific commit until there's a new release +# https://github.com/go-swagger/go-swagger/issues/3070 +SWAGGER := $(GOBIN)/swagger-db51e79a0e37c572d8b59ae0c58bf2bbbbe53285 $(SWAGGER): $(BINGO_DIR)/swagger.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/swagger-v0.30.2" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=swagger.mod -o=$(GOBIN)/swagger-v0.30.2 "github.com/go-swagger/go-swagger/cmd/swagger" + @echo "(re)installing $(GOBIN)/swagger-db51e79a0e37c572d8b59ae0c58bf2bbbbe53285" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=swagger.mod -o=$(GOBIN)/swagger-db51e79a0e37c572d8b59ae0c58bf2bbbbe53285 "github.com/go-swagger/go-swagger/cmd/swagger" diff --git a/.bingo/swagger.mod b/.bingo/swagger.mod index 95c38246f20..ef5b4b6e2c6 100644 --- a/.bingo/swagger.mod +++ b/.bingo/swagger.mod @@ -2,4 +2,4 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT go 1.18 -require github.com/go-swagger/go-swagger v0.30.2 // cmd/swagger +require github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37 // cmd/swagger diff --git a/.bingo/swagger.sum b/.bingo/swagger.sum index a03ffd86341..0dc152e6e87 100644 --- a/.bingo/swagger.sum +++ b/.bingo/swagger.sum @@ -42,8 +42,13 @@ github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJ github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= @@ -51,6 +56,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -73,9 +80,13 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -83,49 +94,71 @@ github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc= github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-openapi/inflect v0.21.0 h1:FoBjBTQEcbg2cJUWX6uwL9OyIW8eqc9k4KhN4lfbeYk= +github.com/go-openapi/inflect v0.21.0/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/loads v0.21.0 h1:jYtUO4wwP7psAweisP/MDoOpdzsYEESdoPcsWjHDR68= github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro= github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= github.com/go-openapi/runtime v0.21.1 h1:/KIG00BzA2x2HRStX2tnhbqbQdPcFlkgsYCiNY20FZs= github.com/go-openapi/runtime v0.24.1 h1:Sml5cgQKGYQHF+M7yYSHaH1eOjvTykrddTE/KtQVjqo= github.com/go-openapi/runtime v0.24.1/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk= +github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= +github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI= github.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= github.com/go-openapi/strfmt v0.21.1 h1:G6s2t5V5kGCHLVbSdZ/6lI8Wm4OzoPFkc3/cjAsKQrM= github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/validate v0.20.3 h1:GZPPhhKSZrE8HjB4eEkoYAZmoWA4+tCemSgINH1/vKw= github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= github.com/go-openapi/validate v0.22.0 h1:b0QecH6VslW/TxtpKgzpO1SNG7GU2FsaqKdP1E2T50Y= github.com/go-openapi/validate v0.22.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= @@ -133,6 +166,10 @@ github.com/go-swagger/go-swagger v0.29.0 h1:z3YoZtLvS1Y8TE/PCat1VypcZxM0IgKLt0Nv github.com/go-swagger/go-swagger v0.29.0/go.mod h1:Z4GJzI+bHKKkGB2Ji1rawpi3/ldXX8CkzGIa9HAC5EE= github.com/go-swagger/go-swagger v0.30.2 h1:23odPUyQZdkNFZZSBJ3mqYYcdh+LnuReEbdWN18OMRo= github.com/go-swagger/go-swagger v0.30.2/go.mod h1:neDPes8r8PCz2JPvHRDj8BTULLh4VJUt7n6MpQqxhHM= +github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37 h1:KFcZmKdZmapAog2+eL1buervAYrYolBZk7fMecPPDmo= +github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37/go.mod h1:i1/E+d8iPNReSE7y04FaVu5OPKB3il5cn+T1Egogg3I= +github.com/go-swagger/go-swagger v0.30.6-0.20240418033037-c46c303aaa02 h1:J6YiT/eg3gAfKMdVCkWXe6khsO+nxa8W4URZ4AUqzbA= +github.com/go-swagger/go-swagger v0.30.6-0.20240418033037-c46c303aaa02/go.mod h1:i1/E+d8iPNReSE7y04FaVu5OPKB3il5cn+T1Egogg3I= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= @@ -211,11 +248,15 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -223,11 +264,16 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= @@ -246,6 +292,8 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -253,6 +301,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -283,6 +333,9 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -297,18 +350,32 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -318,9 +385,12 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -329,9 +399,12 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= @@ -345,6 +418,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.8.2 h1:8ssUXufb90ujcIvR6MyE1SchaNj0SFxsakiZgxIyrMk= @@ -352,12 +426,16 @@ go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCu go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= go.mongodb.org/mongo-driver v1.10.1 h1:NujsPveKwHaWuKUer/ceo9DzEe7HIj1SlJ6uvXZG0S4= go.mongodb.org/mongo-driver v1.10.1/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8= +go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= +go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= @@ -369,10 +447,14 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -383,6 +465,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -409,6 +493,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -444,6 +530,8 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba h1:6u6sik+bn/y7vILcYkK3iwTBWN7WtBvB0+SZswQnbf8= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -465,6 +553,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -511,11 +602,17 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -526,6 +623,9 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -583,6 +683,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= 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= @@ -683,6 +785,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.66.3 h1:jRskFVxYaMGAMUbN0UZ7niA9gzL9B49DOqE78vg0k3w= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/.bingo/variables.env b/.bingo/variables.env index c48e144db22..ab73dbb12d1 100644 --- a/.bingo/variables.env +++ b/.bingo/variables.env @@ -1,4 +1,4 @@ -# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.8. DO NOT EDIT. +# Auto generated binary variables helper managed by https://github.com/bwplotka/bingo v0.9. DO NOT EDIT. # All tools are designed to be build inside $GOBIN. # Those variables will work only until 'bingo get' was invoked, or if tools were installed via Makefile's Variables.mk. GOBIN=${GOBIN:=$(go env GOBIN)} @@ -20,5 +20,5 @@ JB="${GOBIN}/jb-v0.5.1" LEFTHOOK="${GOBIN}/lefthook-v1.4.8" -SWAGGER="${GOBIN}/swagger-v0.30.2" +SWAGGER="${GOBIN}/swagger-v0.30.6-0.20240310114303-db51e79a0e37" diff --git a/.drone.yml b/.drone.yml index b6399fe0c64..3ea9437dc00 100644 --- a/.drone.yml +++ b/.drone.yml @@ -25,7 +25,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - ./bin/build verify-drone @@ -76,14 +76,14 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - go install github.com/bazelbuild/buildtools/buildifier@latest - buildifier --lint=warn -mode=check -r . depends_on: - compile-build-cmd - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: lint-starlark trigger: event: @@ -323,7 +323,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -332,14 +332,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang @@ -347,7 +347,7 @@ steps: -timeout=5m depends_on: - wire-install - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: test-backend - commands: - apk add --update build-base @@ -356,7 +356,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: test-backend-integration trigger: event: @@ -408,7 +408,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - apk add --update curl jq bash @@ -435,7 +435,7 @@ steps: - apk add --update make - make gen-go depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - apk add --update make build-base @@ -444,16 +444,16 @@ steps: - wire-install environment: CGO_ENABLED: "1" - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: lint-backend - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: validate-openapi-spec trigger: event: @@ -512,7 +512,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -522,7 +522,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -531,14 +531,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - apk add --update g++ make python3 && ln -sf /usr/bin/python3 /usr/bin/python @@ -572,7 +572,7 @@ steps: from_secret: drone_token - commands: - /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64 - -a targz:grafana:linux/arm/v7 --go-version=1.21.9 --yarn-cache=$$YARN_CACHE_FOLDER + -a targz:grafana:linux/arm/v7 --go-version=1.22.2 --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER --grafana-dir=$$PWD > packages.txt depends_on: - yarn-install @@ -776,7 +776,7 @@ steps: - /src/grafana-build artifacts -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 --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER - --go-version=1.21.9 --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.19.1 --tag-format='{{ + --go-version=1.22.2 --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.19.1 --tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --grafana-dir=$$PWD --ubuntu-tag-format='{{ .version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' > docker.txt - find ./dist -name '*docker*.tar.gz' -type f | xargs -n1 docker load -i @@ -920,7 +920,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -934,7 +934,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -943,14 +943,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -971,7 +971,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql57:3306 -timeout 120s @@ -992,7 +992,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql57 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: mysql-5.7-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -1013,7 +1013,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -1029,7 +1029,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -1045,7 +1045,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -1061,7 +1061,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: remote-alertmanager-integration-tests trigger: event: @@ -1150,7 +1150,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue trigger: event: @@ -1191,7 +1191,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - apt-get update -yq && apt-get install shellcheck @@ -1263,7 +1263,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: swagger-gen trigger: event: @@ -1359,7 +1359,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -1370,7 +1370,7 @@ steps: - CODEGEN_VERIFY=1 make gen-cue depends_on: - clone-enterprise - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1380,14 +1380,14 @@ steps: - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: - clone-enterprise - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - apk add --update build-base @@ -1395,7 +1395,7 @@ steps: - go test -v -run=^$ -benchmem -timeout=1h -count=8 -bench=. ${GO_PACKAGES} depends_on: - wire-install - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: sqlite-benchmark-integration-tests - commands: - apk add --update build-base @@ -1407,7 +1407,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: postgres-benchmark-integration-tests - commands: - apk add --update build-base @@ -1418,7 +1418,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql57 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: mysql-5.7-benchmark-integration-tests - commands: - apk add --update build-base @@ -1429,7 +1429,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: mysql-8.0-benchmark-integration-tests trigger: event: @@ -1508,7 +1508,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue trigger: branch: main @@ -1685,7 +1685,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1694,14 +1694,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang @@ -1709,7 +1709,7 @@ steps: -timeout=5m depends_on: - wire-install - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: test-backend - commands: - apk add --update build-base @@ -1718,7 +1718,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: test-backend-integration trigger: branch: main @@ -1763,13 +1763,13 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - apk add --update make - make gen-go depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - apk add --update make build-base @@ -1778,16 +1778,16 @@ steps: - wire-install environment: CGO_ENABLED: "1" - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: lint-backend - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: validate-openapi-spec - commands: - ./bin/build verify-drone @@ -1844,7 +1844,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -1854,7 +1854,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1863,14 +1863,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - apk add --update g++ make python3 && ln -sf /usr/bin/python3 /usr/bin/python @@ -1903,7 +1903,7 @@ steps: name: build-frontend-packages - commands: - /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64 - -a targz:grafana:linux/arm/v7 --go-version=1.21.9 --yarn-cache=$$YARN_CACHE_FOLDER + -a targz:grafana:linux/arm/v7 --go-version=1.22.2 --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER --grafana-dir=$$PWD > packages.txt depends_on: - update-package-json-version @@ -2143,7 +2143,7 @@ steps: - /src/grafana-build artifacts -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 --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER - --go-version=1.21.9 --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.19.1 --tag-format='{{ + --go-version=1.22.2 --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.19.1 --tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --grafana-dir=$$PWD --ubuntu-tag-format='{{ .version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' > docker.txt - find ./dist -name '*docker*.tar.gz' -type f | xargs -n1 docker load -i @@ -2349,7 +2349,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -2363,7 +2363,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -2372,14 +2372,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -2400,7 +2400,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql57:3306 -timeout 120s @@ -2421,7 +2421,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql57 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: mysql-5.7-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -2442,7 +2442,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -2458,7 +2458,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -2474,7 +2474,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -2490,7 +2490,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: remote-alertmanager-integration-tests trigger: branch: main @@ -2683,7 +2683,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - ./bin/build artifacts docker fetch --edition oss @@ -2780,7 +2780,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - ./bin/build artifacts packages --tag $${DRONE_TAG} --src-bucket $${PRERELEASE_BUCKET} @@ -2850,7 +2850,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - apk add --update g++ make python3 && ln -sf /usr/bin/python3 /usr/bin/python @@ -2917,7 +2917,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - depends_on: - compile-build-cmd @@ -3024,7 +3024,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.21.9 + GO_VERSION: 1.22.2 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -3082,13 +3082,13 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: compile-build-cmd - commands: - ./bin/build whatsnew-checker depends_on: - compile-build-cmd - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: whats-new-checker trigger: event: @@ -3191,7 +3191,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -3200,14 +3200,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang @@ -3215,7 +3215,7 @@ steps: -timeout=5m depends_on: - wire-install - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: test-backend - commands: - apk add --update build-base @@ -3224,7 +3224,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: test-backend-integration trigger: event: @@ -3281,7 +3281,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.21.9 + GO_VERSION: 1.22.2 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -3464,7 +3464,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.21.9 + GO_VERSION: 1.22.2 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -3614,7 +3614,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -3623,14 +3623,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang @@ -3638,7 +3638,7 @@ steps: -timeout=5m depends_on: - wire-install - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: test-backend - commands: - apk add --update build-base @@ -3647,7 +3647,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: test-backend-integration trigger: cron: @@ -3702,7 +3702,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.21.9 + GO_VERSION: 1.22.2 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -3849,7 +3849,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.21.9 + GO_VERSION: 1.22.2 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -3958,7 +3958,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.21.9 + GO_VERSION: 1.22.2 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4048,20 +4048,20 @@ steps: - commands: [] depends_on: - clone - image: golang:1.21.9-windowsservercore-1809 + image: golang:1.22.2-windowsservercore-1809 name: windows-init - commands: - go install github.com/google/wire/cmd/wire@v0.5.0 - wire gen -tags oss ./pkg/server depends_on: - windows-init - image: golang:1.21.9-windowsservercore-1809 + image: golang:1.22.2-windowsservercore-1809 name: wire-install - commands: - go test -tags requires_buildifer -short -covermode=atomic -timeout=5m ./pkg/... depends_on: - wire-install - image: golang:1.21.9-windowsservercore-1809 + image: golang:1.22.2-windowsservercore-1809 name: test-backend trigger: event: @@ -4154,7 +4154,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -4163,14 +4163,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -4191,7 +4191,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql57:3306 -timeout 120s @@ -4212,7 +4212,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql57 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: mysql-5.7-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -4233,7 +4233,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -4249,7 +4249,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -4265,7 +4265,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -4281,7 +4281,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.21.9-alpine + image: golang:1.22.2-alpine name: remote-alertmanager-integration-tests trigger: event: @@ -4635,7 +4635,7 @@ steps: path: /root/.docker/ - commands: - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine/git:2.40.1 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.21.9-alpine + - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.22.2-alpine - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:20.9.0-alpine - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:20-bookworm - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM google/cloud-sdk:431.0.0 @@ -4670,7 +4670,7 @@ steps: path: /root/.docker/ - commands: - trivy --exit-code 1 --severity HIGH,CRITICAL alpine/git:2.40.1 - - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.21.9-alpine + - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.22.2-alpine - trivy --exit-code 1 --severity HIGH,CRITICAL node:20.9.0-alpine - trivy --exit-code 1 --severity HIGH,CRITICAL node:20-bookworm - trivy --exit-code 1 --severity HIGH,CRITICAL google/cloud-sdk:431.0.0 @@ -4925,6 +4925,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: e67367689de11270bb3139f25a7cabf54e2b980460fd55ad410c590722d122b8 +hmac: 165260ab07061f24b2b206251d8d2ae11eadfc90155af178b0ac8982782c4520 ... diff --git a/.github/workflows/alerting-swagger-gen.yml b/.github/workflows/alerting-swagger-gen.yml index 196ce184e27..aadc1ab2fb9 100644 --- a/.github/workflows/alerting-swagger-gen.yml +++ b/.github/workflows/alerting-swagger-gen.yml @@ -16,7 +16,7 @@ jobs: - name: Set go version uses: actions/setup-go@v4 with: - go-version: '1.21.9' + go-version: '1.22.2' - name: Build swagger run: | make -C pkg/services/ngalert/api/tooling post.json api.json diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index a80003e94b5..7db051c03ea 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -58,7 +58,7 @@ jobs: # Go is required for also updating the schema versions as part of the precommit hook: - uses: actions/setup-go@v4 with: - go-version: '1.21.9' + go-version: '1.22.2' - uses: actions/setup-node@v4 with: node-version: '18' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d768f6c3a5b..094b0eb3213 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: name: Set go version uses: actions/setup-go@v4 with: - go-version: '1.21.9' + go-version: '1.22.2' # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/pr-codeql-analysis-go.yml b/.github/workflows/pr-codeql-analysis-go.yml index 324c6254ce9..216aea74dab 100644 --- a/.github/workflows/pr-codeql-analysis-go.yml +++ b/.github/workflows/pr-codeql-analysis-go.yml @@ -36,7 +36,7 @@ jobs: - name: Set go version uses: actions/setup-go@v4 with: - go-version: '1.21.9' + go-version: '1.22.2' # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/pr-go-workspace-check.yml b/.github/workflows/pr-go-workspace-check.yml index b6b9669a745..655a27c92af 100644 --- a/.github/workflows/pr-go-workspace-check.yml +++ b/.github/workflows/pr-go-workspace-check.yml @@ -22,7 +22,7 @@ jobs: - name: Set go version uses: actions/setup-go@v4 with: - go-version: '1.21.9' + go-version: '1.22.2' - name: Workspace Sync run: go work sync diff --git a/.github/workflows/publish-kinds-next.yml b/.github/workflows/publish-kinds-next.yml index bf3f9f790e8..af33517b338 100644 --- a/.github/workflows/publish-kinds-next.yml +++ b/.github/workflows/publish-kinds-next.yml @@ -36,7 +36,7 @@ jobs: - name: "Setup Go" uses: "actions/setup-go@v4" with: - go-version: '1.21.9' + go-version: '1.22.2' - name: "Verify kinds" run: go run .github/workflows/scripts/kinds/verify-kinds.go diff --git a/.github/workflows/publish-kinds-release.yml b/.github/workflows/publish-kinds-release.yml index 5a7bb9e0428..c7d6eff062c 100644 --- a/.github/workflows/publish-kinds-release.yml +++ b/.github/workflows/publish-kinds-release.yml @@ -39,7 +39,7 @@ jobs: - name: "Setup Go" uses: "actions/setup-go@v4" with: - go-version: '1.21.9' + go-version: '1.22.2' - name: "Verify kinds" run: go run .github/workflows/scripts/kinds/verify-kinds.go diff --git a/.github/workflows/verify-kinds.yml b/.github/workflows/verify-kinds.yml index 33d10b14419..0c335f6f3a4 100644 --- a/.github/workflows/verify-kinds.yml +++ b/.github/workflows/verify-kinds.yml @@ -18,7 +18,7 @@ jobs: - name: "Setup Go" uses: "actions/setup-go@v4" with: - go-version: '1.21.9' + go-version: '1.22.2' - name: "Verify kinds" run: go run .github/workflows/scripts/kinds/verify-kinds.go diff --git a/Dockerfile b/Dockerfile index 0696e3f5b13..7a68afa3b00 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ ARG BASE_IMAGE=alpine:3.19.1 ARG JS_IMAGE=node:20-alpine ARG JS_PLATFORM=linux/amd64 -ARG GO_IMAGE=golang:1.21.9-alpine +ARG GO_IMAGE=golang:1.22.2-alpine ARG GO_SRC=go-builder ARG JS_SRC=js-builder diff --git a/Makefile b/Makefile index 0d47a3d0215..f3eeedbbe89 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ include .bingo/Variables.mk GO = go +GO_VERSION = 1.22.2 GO_FILES ?= ./pkg/... ./pkg/apiserver/... ./pkg/apimachinery/... ./pkg/promlib/... SH_FILES ?= $(shell find ./scripts -name *.sh) GO_BUILD_FLAGS += $(if $(GO_BUILD_DEV),-dev) @@ -316,7 +317,7 @@ build-docker-full-ubuntu: ## Build Docker image based on Ubuntu for development. --build-arg COMMIT_SHA=$$(git rev-parse HEAD) \ --build-arg BUILD_BRANCH=$$(git rev-parse --abbrev-ref HEAD) \ --build-arg BASE_IMAGE=ubuntu:22.04 \ - --build-arg GO_IMAGE=golang:1.21.9 \ + --build-arg GO_IMAGE=golang:$(GO_VERSION) \ --tag grafana/grafana$(TAG_SUFFIX):dev-ubuntu \ $(DOCKER_BUILD_ARGS) diff --git a/public/api-merged.json b/public/api-merged.json index c1c20da0c81..2ddc5d10485 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -11161,7 +11161,7 @@ }, "settings": { "type": "object", - "additionalProperties": false + "additionalProperties": {} } } } @@ -11712,7 +11712,7 @@ }, "model": { "type": "object", - "additionalProperties": false + "additionalProperties": {} }, "queryType": { "type": "string" @@ -12644,7 +12644,15 @@ "type": "string" } }, + "Policies": { + "description": "Policies contains all policy identifiers included in the certificate.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", + "type": "array", + "items": { + "$ref": "#/definitions/OID" + } + }, "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.", "type": "array", "items": { "$ref": "#/definitions/ObjectIdentifier" @@ -12871,15 +12879,15 @@ "properties": { "analytics": { "type": "object", - "additionalProperties": false + "additionalProperties": {} }, "functional": { "type": "object", - "additionalProperties": false + "additionalProperties": {} }, "performance": { "type": "object", - "additionalProperties": false + "additionalProperties": {} } } }, @@ -12946,7 +12954,7 @@ "target": { "description": "Target data query", "type": "object", - "additionalProperties": false, + "additionalProperties": {}, "example": { "prop1": "value1", "prop2": "value" @@ -12974,7 +12982,7 @@ "target": { "description": "Target data query", "type": "object", - "additionalProperties": false, + "additionalProperties": {}, "example": { "prop1": "value1", "prop2": "value" @@ -14275,12 +14283,12 @@ "color": { "description": "Map values to a display color\nNOTE: this interface is under development in the frontend... so simple map for now", "type": "object", - "additionalProperties": false + "additionalProperties": {} }, "custom": { "description": "Panel Specific Values", "type": "object", - "additionalProperties": false + "additionalProperties": {} }, "decimals": { "type": "integer", @@ -15313,7 +15321,7 @@ } }, "IPMask": { - "description": "See type IPNet and func ParseCIDR for details.", + "description": "See type [IPNet] and func [ParseCIDR] for details.", "type": "array", "title": "An IPMask is a bitmask that can be used to manipulate\nIP addresses for IP addressing and routing.", "items": { @@ -16037,7 +16045,7 @@ } }, "Name": { - "description": "Name represents an X.509 distinguished name. This only includes the common\nelements of a DN. Note that Name is only an approximation of the X.509\nstructure. If an accurate representation is needed, asn1.Unmarshal the raw\nsubject or issuer as an RDNSequence.", + "description": "Name represents an X.509 distinguished name. This only includes the common\nelements of a DN. Note that Name is only an approximation of the X.509\nstructure. If an accurate representation is needed, asn1.Unmarshal the raw\nsubject or issuer as an [RDNSequence].", "type": "object", "properties": { "Country": { @@ -16275,6 +16283,10 @@ } } }, + "OID": { + "type": "object", + "title": "An OID represents an ASN.1 OBJECT IDENTIFIER." + }, "ObjectIdentifier": { "type": "array", "title": "An ObjectIdentifier represents an ASN.1 OBJECT IDENTIFIER.", @@ -17565,7 +17577,7 @@ "properties": { "extra": { "type": "object", - "additionalProperties": false + "additionalProperties": {} }, "message": { "type": "string" @@ -17748,12 +17760,12 @@ "color": { "description": "Map values to a display color\nNOTE: this interface is under development in the frontend... so simple map for now", "type": "object", - "additionalProperties": false + "additionalProperties": {} }, "custom": { "description": "Panel Specific Values", "type": "object", - "additionalProperties": false + "additionalProperties": {} }, "decimals": { "type": "integer", @@ -17990,7 +18002,7 @@ "type": "array", "items": { "type": "object", - "additionalProperties": false + "additionalProperties": {} } }, "range": { @@ -20069,7 +20081,7 @@ } }, "URL": { - "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the EscapedPath method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.", + "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nThe Host field contains the host and port subcomponents of the URL.\nWhen the port is present, it is separated from the host with a colon.\nWhen the host is an IPv6 address, it must be enclosed in square brackets:\n\"[fe80::1]:80\". The [net.JoinHostPort] function combines a host and port\ninto a string suitable for the Host field, adding square brackets to\nthe host when necessary.\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the [URL.EscapedPath] method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.", "type": "object", "title": "A URL represents a parsed URL (technically, a URI reference).", "properties": { @@ -20115,7 +20127,7 @@ "Object": { "description": "Object is a JSON compatible map with string, float, int, bool, []interface{},\nor map[string]interface{} children.", "type": "object", - "additionalProperties": false + "additionalProperties": {} } } }, @@ -20707,7 +20719,7 @@ } }, "Userinfo": { - "description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a URL. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.", + "description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a [URL]. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.", "type": "object" }, "ValidationError": { @@ -21349,7 +21361,7 @@ "extra": { "description": "Extra Additional information about the error", "type": "object", - "additionalProperties": false + "additionalProperties": {} }, "message": { "description": "Message A human readable message", @@ -22364,7 +22376,7 @@ }, "settings": { "type": "object", - "additionalProperties": false + "additionalProperties": {} }, "source": { "type": "string" @@ -22565,7 +22577,7 @@ }, "settings": { "type": "object", - "additionalProperties": false + "additionalProperties": {} }, "source": { "type": "string" diff --git a/public/openapi3.json b/public/openapi3.json index fa944a233dc..5306e9f14bc 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -1179,7 +1179,7 @@ "type": "string" }, "settings": { - "additionalProperties": false, + "additionalProperties": {}, "type": "object" }, "source": { @@ -1459,7 +1459,7 @@ "type": "string" }, "settings": { - "additionalProperties": false, + "additionalProperties": {}, "type": "object" }, "source": { @@ -2482,7 +2482,7 @@ "type": "string" }, "model": { - "additionalProperties": false, + "additionalProperties": {}, "type": "object" }, "queryType": { @@ -3415,7 +3415,15 @@ }, "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.", + "items": { + "$ref": "#/components/schemas/OID" + }, + "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.", "items": { "$ref": "#/components/schemas/ObjectIdentifier" }, @@ -3641,15 +3649,15 @@ "CookiePreferences": { "properties": { "analytics": { - "additionalProperties": false, + "additionalProperties": {}, "type": "object" }, "functional": { - "additionalProperties": false, + "additionalProperties": {}, "type": "object" }, "performance": { - "additionalProperties": false, + "additionalProperties": {}, "type": "object" } }, @@ -3711,7 +3719,7 @@ "type": "string" }, "target": { - "additionalProperties": false, + "additionalProperties": {}, "description": "Target data query", "example": { "prop1": "value1", @@ -3744,7 +3752,7 @@ "type": "string" }, "target": { - "additionalProperties": false, + "additionalProperties": {}, "description": "Target data query", "example": { "prop1": "value1", @@ -5044,12 +5052,12 @@ "FieldConfig": { "properties": { "color": { - "additionalProperties": false, + "additionalProperties": {}, "description": "Map values to a display color\nNOTE: this interface is under development in the frontend... so simple map for now", "type": "object" }, "custom": { - "additionalProperties": false, + "additionalProperties": {}, "description": "Panel Specific Values", "type": "object" }, @@ -6086,7 +6094,7 @@ "type": "object" }, "IPMask": { - "description": "See type IPNet and func ParseCIDR for details.", + "description": "See type [IPNet] and func [ParseCIDR] for details.", "items": { "format": "uint8", "type": "integer" @@ -6810,7 +6818,7 @@ "type": "array" }, "Name": { - "description": "Name represents an X.509 distinguished name. This only includes the common\nelements of a DN. Note that Name is only an approximation of the X.509\nstructure. If an accurate representation is needed, asn1.Unmarshal the raw\nsubject or issuer as an RDNSequence.", + "description": "Name represents an X.509 distinguished name. This only includes the common\nelements of a DN. Note that Name is only an approximation of the X.509\nstructure. If an accurate representation is needed, asn1.Unmarshal the raw\nsubject or issuer as an [RDNSequence].", "properties": { "Country": { "items": { @@ -7048,6 +7056,10 @@ "title": "OAuth2 is the oauth2 client configuration.", "type": "object" }, + "OID": { + "title": "An OID represents an ASN.1 OBJECT IDENTIFIER.", + "type": "object" + }, "ObjectIdentifier": { "items": { "format": "int64", @@ -8336,7 +8348,7 @@ "description": "PublicError is derived from Error and only contains information\navailable to the end user.", "properties": { "extra": { - "additionalProperties": false, + "additionalProperties": {}, "type": "object" }, "message": { @@ -8517,12 +8529,12 @@ "description": "The embedded FieldConfig's display name must be set.\nIt corresponds to the QueryResultMetaStat on the frontend (https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/types/data.ts#L53).", "properties": { "color": { - "additionalProperties": false, + "additionalProperties": {}, "description": "Map values to a display color\nNOTE: this interface is under development in the frontend... so simple map for now", "type": "object" }, "custom": { - "additionalProperties": false, + "additionalProperties": {}, "description": "Panel Specific Values", "type": "object" }, @@ -8760,7 +8772,7 @@ }, "queries": { "items": { - "additionalProperties": false, + "additionalProperties": {}, "type": "object" }, "type": "array" @@ -10841,7 +10853,7 @@ "type": "object" }, "URL": { - "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the EscapedPath method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.", + "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nThe Host field contains the host and port subcomponents of the URL.\nWhen the port is present, it is separated from the host with a colon.\nWhen the host is an IPv6 address, it must be enclosed in square brackets:\n\"[fe80::1]:80\". The [net.JoinHostPort] function combines a host and port\ninto a string suitable for the Host field, adding square brackets to\nthe host when necessary.\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the [URL.EscapedPath] method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.", "properties": { "ForceQuery": { "type": "boolean" @@ -10884,7 +10896,7 @@ "description": "Unstructured allows objects that do not have Golang structs registered to be manipulated\ngenerically.", "properties": { "Object": { - "additionalProperties": false, + "additionalProperties": {}, "description": "Object is a JSON compatible map with string, float, int, bool, []interface{},\nor map[string]interface{} children.", "type": "object" } @@ -11479,7 +11491,7 @@ "type": "object" }, "Userinfo": { - "description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a URL. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.", + "description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a [URL]. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.", "type": "object" }, "ValidationError": { @@ -12114,7 +12126,7 @@ "description": "PublicError is derived from Error and only contains information\navailable to the end user.", "properties": { "extra": { - "additionalProperties": false, + "additionalProperties": {}, "description": "Extra Additional information about the error", "type": "object" }, @@ -24669,7 +24681,7 @@ "type": "string" }, "settings": { - "additionalProperties": false, + "additionalProperties": {}, "type": "object" } }, diff --git a/scripts/drone/variables.star b/scripts/drone/variables.star index 1024b73e5c5..07e2768f0c7 100644 --- a/scripts/drone/variables.star +++ b/scripts/drone/variables.star @@ -3,7 +3,7 @@ global variables """ grabpl_version = "v3.0.50" -golang_version = "1.21.9" +golang_version = "1.22.2" # nodejs_version should match what's in ".nvmrc", but without the v prefix. nodejs_version = "20.9.0" From a3ef4634992e340c14a8f21647944722fc847966 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 25 Apr 2024 07:12:43 +0200 Subject: [PATCH 098/222] Grafana packages: Remove E2E workspace (#86416) * remove e2e package code and any code referencing it * update code owners * remove more references to e2e package * remove unrelated file --- .betterer.results | 3 - .betterer.results.json | 4 - .betterer.ts | 1 - .eslintrc | 68 +- .github/CODEOWNERS | 1 - .github/renovate.json5 | 2 +- LICENSING.md | 1 - packages/grafana-data/.eslintrc | 10 +- packages/grafana-e2e/.gitignore | 3 - packages/grafana-e2e/CHANGELOG.md | 0 packages/grafana-e2e/LICENSE_APACHE2 | 202 - packages/grafana-e2e/README.md | 5 - packages/grafana-e2e/bin/grafana-e2e.js | 3 - packages/grafana-e2e/cli.js | 57 - packages/grafana-e2e/cypress.json | 7 - .../grafana-e2e/cypress/fixtures/example.json | 5 - .../fixtures/exemplars-query-response.json | 323 - .../cypress/fixtures/long-trace-response.json | 7592 ----------------- .../prometheus-query-range-response.json | 81 - .../fixtures/prometheus-query-response.json | 4 - .../cypress/fixtures/tempo-response.json | 1181 --- .../plugins/benchmark/CDPDataCollector.ts | 136 - .../plugins/benchmark/DataCollector.ts | 14 - .../cypress/plugins/benchmark/formatting.ts | 139 - .../cypress/plugins/benchmark/index.ts | 88 - .../cypress/plugins/benchmark/tracelib.d.ts | 15 - .../cypress/plugins/compareScreenshots.js | 49 - .../cypress/plugins/extendConfig.js | 79 - packages/grafana-e2e/cypress/plugins/index.js | 73 - .../cypress/plugins/readProvisions.js | 14 - .../cypress/plugins/typescriptPreprocessor.js | 42 - .../grafana-e2e/cypress/support/commands.ts | 41 - .../grafana-e2e/cypress/support/index.d.ts | 12 - packages/grafana-e2e/cypress/support/index.ts | 49 - packages/grafana-e2e/cypress/tsconfig.json | 9 - packages/grafana-e2e/package.json | 89 - packages/grafana-e2e/rollup.config.ts | 29 - .../grafana-e2e/src/flows/addDashboard.ts | 303 - .../grafana-e2e/src/flows/addDataSource.ts | 116 - packages/grafana-e2e/src/flows/addPanel.ts | 15 - .../src/flows/assertSuccessNotification.ts | 9 - .../grafana-e2e/src/flows/configurePanel.ts | 192 - .../grafana-e2e/src/flows/deleteDashboard.ts | 51 - .../grafana-e2e/src/flows/deleteDataSource.ts | 46 - packages/grafana-e2e/src/flows/editPanel.ts | 7 - .../grafana-e2e/src/flows/importDashboard.ts | 70 - .../grafana-e2e/src/flows/importDashboards.ts | 21 - packages/grafana-e2e/src/flows/index.ts | 36 - packages/grafana-e2e/src/flows/login.ts | 42 - .../grafana-e2e/src/flows/openDashboard.ts | 36 - .../src/flows/openPanelMenuItem.ts | 57 - .../grafana-e2e/src/flows/revertAllChanges.ts | 12 - .../grafana-e2e/src/flows/saveDashboard.ts | 9 - .../grafana-e2e/src/flows/selectOption.ts | 43 - .../src/flows/setDashboardTimeRange.ts | 5 - .../grafana-e2e/src/flows/setTimeRange.ts | 40 - .../grafana-e2e/src/flows/userPreferences.ts | 25 - packages/grafana-e2e/src/index.ts | 31 - packages/grafana-e2e/src/support/benchmark.ts | 81 - packages/grafana-e2e/src/support/index.ts | 4 - .../grafana-e2e/src/support/localStorage.ts | 23 - packages/grafana-e2e/src/support/scenario.ts | 51 - .../src/support/scenarioContext.ts | 61 - packages/grafana-e2e/src/support/selector.ts | 11 - packages/grafana-e2e/src/support/types.ts | 138 - packages/grafana-e2e/src/support/url.ts | 14 - packages/grafana-e2e/src/typings/index.ts | 1 - packages/grafana-e2e/src/typings/undo.ts | 19 - .../test/cypress/integration/0.cli.ts | 3 - .../test/cypress/integration/1.api.ts | 7 - .../grafana-e2e/test/cypress/tsconfig.json | 8 - packages/grafana-e2e/tsconfig.build.json | 4 - packages/grafana-e2e/tsconfig.json | 12 - packages/grafana-ui/.eslintrc | 20 +- pkg/build/npm/npm.go | 1 - yarn.lock | 1173 +-- 76 files changed, 197 insertions(+), 13031 deletions(-) delete mode 100644 packages/grafana-e2e/.gitignore delete mode 100644 packages/grafana-e2e/CHANGELOG.md delete mode 100644 packages/grafana-e2e/LICENSE_APACHE2 delete mode 100644 packages/grafana-e2e/README.md delete mode 100755 packages/grafana-e2e/bin/grafana-e2e.js delete mode 100644 packages/grafana-e2e/cli.js delete mode 100644 packages/grafana-e2e/cypress.json delete mode 100644 packages/grafana-e2e/cypress/fixtures/example.json delete mode 100644 packages/grafana-e2e/cypress/fixtures/exemplars-query-response.json delete mode 100644 packages/grafana-e2e/cypress/fixtures/long-trace-response.json delete mode 100644 packages/grafana-e2e/cypress/fixtures/prometheus-query-range-response.json delete mode 100644 packages/grafana-e2e/cypress/fixtures/prometheus-query-response.json delete mode 100644 packages/grafana-e2e/cypress/fixtures/tempo-response.json delete mode 100644 packages/grafana-e2e/cypress/plugins/benchmark/CDPDataCollector.ts delete mode 100644 packages/grafana-e2e/cypress/plugins/benchmark/DataCollector.ts delete mode 100644 packages/grafana-e2e/cypress/plugins/benchmark/formatting.ts delete mode 100644 packages/grafana-e2e/cypress/plugins/benchmark/index.ts delete mode 100644 packages/grafana-e2e/cypress/plugins/benchmark/tracelib.d.ts delete mode 100644 packages/grafana-e2e/cypress/plugins/compareScreenshots.js delete mode 100644 packages/grafana-e2e/cypress/plugins/extendConfig.js delete mode 100644 packages/grafana-e2e/cypress/plugins/index.js delete mode 100644 packages/grafana-e2e/cypress/plugins/readProvisions.js delete mode 100644 packages/grafana-e2e/cypress/plugins/typescriptPreprocessor.js delete mode 100644 packages/grafana-e2e/cypress/support/commands.ts delete mode 100644 packages/grafana-e2e/cypress/support/index.d.ts delete mode 100644 packages/grafana-e2e/cypress/support/index.ts delete mode 100644 packages/grafana-e2e/cypress/tsconfig.json delete mode 100644 packages/grafana-e2e/package.json delete mode 100644 packages/grafana-e2e/rollup.config.ts delete mode 100644 packages/grafana-e2e/src/flows/addDashboard.ts delete mode 100644 packages/grafana-e2e/src/flows/addDataSource.ts delete mode 100644 packages/grafana-e2e/src/flows/addPanel.ts delete mode 100644 packages/grafana-e2e/src/flows/assertSuccessNotification.ts delete mode 100644 packages/grafana-e2e/src/flows/configurePanel.ts delete mode 100644 packages/grafana-e2e/src/flows/deleteDashboard.ts delete mode 100644 packages/grafana-e2e/src/flows/deleteDataSource.ts delete mode 100644 packages/grafana-e2e/src/flows/editPanel.ts delete mode 100644 packages/grafana-e2e/src/flows/importDashboard.ts delete mode 100644 packages/grafana-e2e/src/flows/importDashboards.ts delete mode 100644 packages/grafana-e2e/src/flows/index.ts delete mode 100644 packages/grafana-e2e/src/flows/login.ts delete mode 100644 packages/grafana-e2e/src/flows/openDashboard.ts delete mode 100644 packages/grafana-e2e/src/flows/openPanelMenuItem.ts delete mode 100644 packages/grafana-e2e/src/flows/revertAllChanges.ts delete mode 100644 packages/grafana-e2e/src/flows/saveDashboard.ts delete mode 100644 packages/grafana-e2e/src/flows/selectOption.ts delete mode 100644 packages/grafana-e2e/src/flows/setDashboardTimeRange.ts delete mode 100644 packages/grafana-e2e/src/flows/setTimeRange.ts delete mode 100644 packages/grafana-e2e/src/flows/userPreferences.ts delete mode 100644 packages/grafana-e2e/src/index.ts delete mode 100644 packages/grafana-e2e/src/support/benchmark.ts delete mode 100644 packages/grafana-e2e/src/support/index.ts delete mode 100644 packages/grafana-e2e/src/support/localStorage.ts delete mode 100644 packages/grafana-e2e/src/support/scenario.ts delete mode 100644 packages/grafana-e2e/src/support/scenarioContext.ts delete mode 100644 packages/grafana-e2e/src/support/selector.ts delete mode 100644 packages/grafana-e2e/src/support/types.ts delete mode 100644 packages/grafana-e2e/src/support/url.ts delete mode 100644 packages/grafana-e2e/src/typings/index.ts delete mode 100644 packages/grafana-e2e/src/typings/undo.ts delete mode 100644 packages/grafana-e2e/test/cypress/integration/0.cli.ts delete mode 100644 packages/grafana-e2e/test/cypress/integration/1.api.ts delete mode 100644 packages/grafana-e2e/test/cypress/tsconfig.json delete mode 100644 packages/grafana-e2e/tsconfig.build.json delete mode 100644 packages/grafana-e2e/tsconfig.json diff --git a/.betterer.results b/.betterer.results index 1fec2f3f3ed..87fef9367ea 100644 --- a/.betterer.results +++ b/.betterer.results @@ -6353,9 +6353,6 @@ exports[`no gf-form usage`] = { "e2e/utils/flows/addDataSource.ts:5381": [ [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] ], - "packages/grafana-e2e/src/flows/addDataSource.ts:5381": [ - [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] - ], "packages/grafana-prometheus/src/components/PromExploreExtraField.tsx:5381": [ [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], diff --git a/.betterer.results.json b/.betterer.results.json index 5b11f01cfca..7ee6947ecb6 100644 --- a/.betterer.results.json +++ b/.betterer.results.json @@ -7005,10 +7005,6 @@ "path": "/e2e/utils/flows/addDataSource.ts", "count": 1 }, - { - "path": "/packages/grafana-e2e/src/flows/addDataSource.ts", - "count": 1 - }, { "path": "/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx", "count": 4 diff --git a/.betterer.ts b/.betterer.ts index 44267d58a5a..82e1a09cfe3 100644 --- a/.betterer.ts +++ b/.betterer.ts @@ -7,7 +7,6 @@ import { glob } from 'glob'; // Why are we ignoring these? // They're all deprecated/being removed so doesn't make sense to fix types const eslintPathsToIgnore = [ - 'packages/grafana-e2e', // deprecated. 'public/app/angular', // will be removed in Grafana 11 'public/app/plugins/panel/graph', // will be removed alongside angular 'public/app/plugins/panel/table-old', // will be removed alongside angular diff --git a/.eslintrc b/.eslintrc index dac72ca97e0..c61459edfad 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,7 @@ "plugins": ["@emotion", "lodash", "jest", "import", "jsx-a11y", "@grafana", "no-barrel-files"], "settings": { "import/internal-regex": "^(app/)|(@grafana)", - "import/external-module-folders": ["node_modules", ".yarn"] + "import/external-module-folders": ["node_modules", ".yarn"], }, "rules": { "@grafana/no-border-radius-literal": "error", @@ -19,8 +19,8 @@ { "groups": [["builtin", "external"], "internal", "parent", "sibling", "index"], "newlines-between": "always", - "alphabetize": { "order": "asc" } - } + "alphabetize": { "order": "asc" }, + }, ], "no-restricted-imports": [ "error", @@ -29,47 +29,43 @@ { "name": "react-redux", "importNames": ["useDispatch", "useSelector"], - "message": "Please import from app/types instead." + "message": "Please import from app/types instead.", }, { "name": "react-i18next", "importNames": ["Trans", "t"], - "message": "Please import from app/core/internationalization instead" + "message": "Please import from app/core/internationalization instead", }, - { - "name": "@grafana/e2e", - "message": "@grafana/e2e is deprecated. Please import from ./e2e/utils instead" - } - ] - } + ], + }, ], // Use typescript's no-redeclare for compatibility with overrides "no-redeclare": "off", - "@typescript-eslint/no-redeclare": ["error"] + "@typescript-eslint/no-redeclare": ["error"], }, "overrides": [ { "files": ["packages/grafana-ui/src/components/uPlot/**/*.{ts,tsx}"], "rules": { "react-hooks/rules-of-hooks": "off", - "react-hooks/exhaustive-deps": "off" - } + "react-hooks/exhaustive-deps": "off", + }, }, { "files": ["packages/grafana-ui/src/components/ThemeDemos/**/*.{ts,tsx}"], "rules": { "@emotion/jsx-import": "off", "react/jsx-uses-react": "off", - "react/react-in-jsx-scope": "off" - } + "react/react-in-jsx-scope": "off", + }, }, { "files": ["public/dashboards/scripted*.js"], "rules": { "no-redeclare": "error", - "@typescript-eslint/no-redeclare": "off" - } + "@typescript-eslint/no-redeclare": "off", + }, }, { "extends": ["plugin:jsx-a11y/recommended"], @@ -82,17 +78,17 @@ "jsx-a11y/no-autofocus": [ "error", { - "ignoreNonDOM": true - } + "ignoreNonDOM": true, + }, ], "jsx-a11y/label-has-associated-control": [ "error", { "controlComponents": ["NumberInput"], - "depth": 2 - } - ] - } + "depth": 2, + }, + ], + }, }, { "files": [ @@ -125,14 +121,14 @@ "public/app/plugins/datasource/cloudwatch/*.{ts,tsx}", "public/app/plugins/datasource/cloudwatch/**/*.{ts,tsx}", "public/app/plugins/datasource/zipkin/*.{ts,tsx}", - "public/app/plugins/datasource/zipkin/**/*.{ts,tsx}" + "public/app/plugins/datasource/zipkin/**/*.{ts,tsx}", ], "settings": { "import/resolver": { "node": { - "extensions": [".ts", ".tsx"] - } - } + "extensions": [".ts", ".tsx"], + }, + }, }, "rules": { "import/no-restricted-paths": [ @@ -143,12 +139,12 @@ "target": "./public/app/plugins", "from": "./public", "except": ["./app/plugins"], - "message": "Core plugins are not allowed to depend on Grafana core packages" - } - ] - } - ] - } - } - ] + "message": "Core plugins are not allowed to depend on Grafana core packages", + }, + ], + }, + ], + }, + }, + ], } diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f6ba469e722..b7ec17d0319 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -313,7 +313,6 @@ /e2e/plugin-e2e/plugin-e2e-api-tests/ @grafana/plugins-platform-frontend /packages/ @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend /packages/grafana-e2e-selectors/ @grafana/grafana-frontend-platform -/packages/grafana-e2e/ @grafana/grafana-frontend-platform /packages/grafana-ui/.storybook/ @grafana/plugins-platform-frontend /packages/grafana-ui/src/components/ @grafana/grafana-frontend-platform /packages/grafana-ui/src/components/DateTimePickers/ @grafana/grafana-frontend-platform diff --git a/.github/renovate.json5 b/.github/renovate.json5 index b78d893ab3c..25f18c606db 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -13,7 +13,7 @@ "@swc/core", // versions ~1.4.5 contain multiple bugs related to baseUrl resolution breaking builds. ], "includePaths": ["package.json", "packages/**", "public/app/plugins/**"], - "ignorePaths": ["emails/**", "plugins-bundled/**", "**/mocks/**", "packages/grafana-e2e/**"], + "ignorePaths": ["emails/**", "plugins-bundled/**", "**/mocks/**"], "labels": ["area/frontend", "dependencies", "no-changelog"], "postUpdateOptions": ["yarnDedupeHighest"], "packageRules": [ diff --git a/LICENSING.md b/LICENSING.md index 97f2c53d8e7..5239154d161 100644 --- a/LICENSING.md +++ b/LICENSING.md @@ -10,7 +10,6 @@ The following directories and their subdirectories are licensed under Apache-2.0 ``` packages/grafana-data/ -packages/grafana-e2e/ packages/grafana-e2e-selectors/ packages/grafana-runtime/ packages/grafana-ui/ diff --git a/packages/grafana-data/.eslintrc b/packages/grafana-data/.eslintrc index 41e607e143b..a9902770dcf 100644 --- a/packages/grafana-data/.eslintrc +++ b/packages/grafana-data/.eslintrc @@ -1,13 +1,13 @@ { "rules": { - "no-restricted-imports": ["error", { "patterns": ["@grafana/runtime", "@grafana/ui", "@grafana/data", "@grafana/e2e/*"] }] + "no-restricted-imports": ["error", { "patterns": ["@grafana/runtime", "@grafana/ui", "@grafana/data"] }], }, "overrides": [ { "files": ["**/*.test.{ts,tsx}"], "rules": { - "no-restricted-imports": "off" - } - } - ] + "no-restricted-imports": "off", + }, + }, + ], } diff --git a/packages/grafana-e2e/.gitignore b/packages/grafana-e2e/.gitignore deleted file mode 100644 index 9770e5ad330..00000000000 --- a/packages/grafana-e2e/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -test/cypress/report.json -test/cypress/screenshots/actual -test/cypress/videos/ diff --git a/packages/grafana-e2e/CHANGELOG.md b/packages/grafana-e2e/CHANGELOG.md deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/grafana-e2e/LICENSE_APACHE2 b/packages/grafana-e2e/LICENSE_APACHE2 deleted file mode 100644 index 373dde574a0..00000000000 --- a/packages/grafana-e2e/LICENSE_APACHE2 +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2015 Grafana Labs - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/grafana-e2e/README.md b/packages/grafana-e2e/README.md deleted file mode 100644 index a488db87725..00000000000 --- a/packages/grafana-e2e/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Grafana End-to-End Test library - -> [!CAUTION] -> This package is deprecated. -> If you'd like to write end-to-end tests for a Grafana plugin (core or external), use the [`@grafana/plugin-e2e`](https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/introduction) package. diff --git a/packages/grafana-e2e/bin/grafana-e2e.js b/packages/grafana-e2e/bin/grafana-e2e.js deleted file mode 100755 index e2aefb16e00..00000000000 --- a/packages/grafana-e2e/bin/grafana-e2e.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node - -require('../cli')(); diff --git a/packages/grafana-e2e/cli.js b/packages/grafana-e2e/cli.js deleted file mode 100644 index b35338a9195..00000000000 --- a/packages/grafana-e2e/cli.js +++ /dev/null @@ -1,57 +0,0 @@ -const { program } = require('commander'); -const execa = require('execa'); -const { resolve, sep } = require('path'); -const resolveBin = require('resolve-bin'); - -const cypress = (commandName, { updateScreenshots, browser }) => { - // Support running an unpublished dev build - const dirname = __dirname.split(sep).pop(); - const projectPath = resolve(`${__dirname}${dirname === 'dist' ? '/..' : ''}`); - - // For plugins/extendConfig - const CWD = `CWD=${process.cwd()}`; - - // For plugins/compareSnapshots - const UPDATE_SCREENSHOTS = `UPDATE_SCREENSHOTS=${updateScreenshots ? 1 : 0}`; - - const cypressOptions = [commandName, '--env', `${CWD},${UPDATE_SCREENSHOTS}`, `--project=${projectPath}`]; - - if (browser) { - cypressOptions.push('--browser', browser); - } - - const execaOptions = { - cwd: __dirname, - stdio: 'inherit', - }; - - return execa(resolveBin.sync('cypress'), cypressOptions, execaOptions) - .then(() => {}) // no return value - .catch((error) => { - console.error(error.message); - process.exitCode = 1; - }); -}; - -module.exports = () => { - const updateOption = '-u, --update-screenshots'; - const updateDescription = 'update expected screenshots'; - const browserOption = '-b, --browser '; - const browserDescription = 'specify which browser to use'; - - program - .command('open') - .description('runs tests within the interactive GUI') - .option(updateOption, updateDescription) - .option(browserOption, browserDescription) - .action((options) => cypress('open', options)); - - program - .command('run') - .description('runs tests from the CLI without the GUI') - .option(updateOption, updateDescription) - .option(browserOption, browserDescription) - .action((options) => cypress('run', options)); - - program.parse(process.argv); -}; diff --git a/packages/grafana-e2e/cypress.json b/packages/grafana-e2e/cypress.json deleted file mode 100644 index 7eafaf4bf2b..00000000000 --- a/packages/grafana-e2e/cypress.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "projectId": "zb7k1c", - "supportFile": "cypress/support/index.ts", - "videoCompression": 20, - "viewportWidth": 1920, - "viewportHeight": 1080 -} diff --git a/packages/grafana-e2e/cypress/fixtures/example.json b/packages/grafana-e2e/cypress/fixtures/example.json deleted file mode 100644 index 02e4254378e..00000000000 --- a/packages/grafana-e2e/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} diff --git a/packages/grafana-e2e/cypress/fixtures/exemplars-query-response.json b/packages/grafana-e2e/cypress/fixtures/exemplars-query-response.json deleted file mode 100644 index 6c26a9fbf7d..00000000000 --- a/packages/grafana-e2e/cypress/fixtures/exemplars-query-response.json +++ /dev/null @@ -1,323 +0,0 @@ -{ - "results": { - "A": { - "frames": [ - { - "schema": { - "name": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))", - "refId": "A", - "meta": { "custom": { "resultType": "matrix" } }, - "fields": [ - { "name": "Time", "type": "time", "typeInfo": { "frame": "time.Time" } }, - { - "name": "Value", - "type": "number", - "typeInfo": { "frame": "float64" }, - "labels": {}, - "config": { - "displayNameFromDS": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))" - } - } - ] - }, - "data": { - "values": [ - [ - 1633619595000, 1633619610000, 1633619625000, 1633619640000, 1633619655000, 1633619670000, 1633619685000, - 1633619700000, 1633619715000, 1633619730000, 1633619745000, 1633619760000, 1633619775000, 1633619790000, - 1633619805000, 1633619820000, 1633619835000, 1633619850000, 1633619865000, 1633619880000, 1633619895000 - ], - [ - 0.07245212135073513, 0.07253198890830721, 0.07247862573797707, 0.07238248338231042, 0.07221687487740913, - 0.07223291298743946, 0.07225427016727755, 0.024531677091864545, 0.02317081920915543, - 0.07548902139580993, 0.0777721702857508, 0.07768649905047344, 0.07782257603228229, 0.07788810213200052, - 0.07791835055437593, 0.07798387201529966, 0.07790826751849372, 0.07794858648610933, 0.07778729925797964, - 0.07769657495236215, 0.077550401329267 - ] - ] - } - }, - { - "schema": { - "name": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))", - "refId": "A", - "meta": { "custom": { "resultType": "vector" } }, - "fields": [ - { "name": "Time", "type": "time", "typeInfo": { "frame": "time.Time" } }, - { - "name": "Value", - "type": "number", - "typeInfo": { "frame": "float64" }, - "labels": {}, - "config": { - "displayNameFromDS": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))" - } - } - ] - }, - "data": { "values": [[1633619900000], [0.0775504013292671]] } - }, - { - "schema": { - "name": "exemplar", - "refId": "A", - "meta": { "custom": { "resultType": "exemplar" } }, - "fields": [ - { "name": "Time", "type": "time", "typeInfo": { "frame": "time.Time" } }, - { "name": "Value", "type": "number", "typeInfo": { "frame": "float64" } }, - { "name": "instance", "type": "string", "typeInfo": { "frame": "string" } }, - { "name": "__name__", "type": "string", "typeInfo": { "frame": "string" } }, - { "name": "job", "type": "string", "typeInfo": { "frame": "string" } }, - { "name": "status_code", "type": "string", "typeInfo": { "frame": "string" } }, - { "name": "method", "type": "string", "typeInfo": { "frame": "string" } }, - { "name": "traceID", "type": "string", "typeInfo": { "frame": "string" } }, - { "name": "route", "type": "string", "typeInfo": { "frame": "string" } }, - { "name": "ws", "type": "string", "typeInfo": { "frame": "string" } }, - { "name": "le", "type": "string", "typeInfo": { "frame": "string" } } - ] - }, - "data": { - "values": [ - [ - 1633619598000, 1633619622000, 1633619625000, 1633619646000, 1633619658000, 1633619682000, 1633619695000, - 1633619712000, 1633619712000, 1633619724000, 1633619717000, 1633619742000, 1633619757000, 1633619771000, - 1633619784000, 1633619801000, 1633619806000, 1633619833000, 1633619833000, 1633619845000, 1633619862000, - 1633619877000, 1633619889000 - ], - [ - 0.0146153, 0.0118506, 0.0473847, 0.026997, 0.0164318, 0.0113532, 0.0105197, 0.162789, 0.0556026, - 0.148856, 0.0433809, 0.0117758, 0.0114496, 0.0114099, 0.0421927, 0.0134148, 0.0152827, 0.6975967, - 0.0394788, 0.0137441, 0.0110939, 0.0104496, 0.0101284 - ], - [ - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "db:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80", - "app:80" - ], - [ - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket", - "tns_request_duration_seconds_bucket" - ], - [ - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/db", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app", - "tns/app" - ], - [ - "302", - "200", - "200", - "200", - "200", - "200", - "200", - "500", - "200", - "302", - "208", - "200", - "200", - "200", - "200", - "200", - "302", - "200", - "200", - "200", - "200", - "200", - "200" - ], - [ - "POST", - "GET", - "GET", - "GET", - "GET", - "GET", - "GET", - "GET", - "GET", - "POST", - "POST", - "GET", - "GET", - "GET", - "GET", - "GET", - "POST", - "GET", - "GET", - "GET", - "GET", - "GET", - "GET" - ], - [ - "6a3cf561ef6c32a0", - "396bcdf29601a149", - "57c04ef608f11158", - "77c757dab83c665f", - "3d1069567e873f5e", - "b337949f6213efd", - "21b20cbe533cf099", - "2c10b3aa30fabd66", - "42ac6088a757636b", - "2f81158008cd4dcc", - "320b803ad7323b37", - "7f15fd82aeb8b361", - "11c79266da8a74cd", - "5a8571bdcc04c990", - "3de3f4f42ccb93ae", - "23343ac91cc0638", - "5cea3aad17ab11c8", - "5d334e2843d3405a", - "3cf6834596d4b6b6", - "1ab6cff012959723", - "2f78bc2c398b8b20", - "6d5862a70c3abd42", - "f5421be4054f501" - ], - [ - "post", - "root", - "root", - "root", - "root", - "root", - "root", - "root", - "root", - "post", - "post", - "root", - "root", - "root", - "root", - "root", - "post", - "metrics", - "root", - "root", - "root", - "root", - "root" - ], - [ - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false", - "false" - ], - [ - "0.025", - "0.025", - "0.05", - "0.05", - "0.025", - "0.025", - "0.025", - "0.25", - "0.1", - "0.25", - "0.05", - "0.025", - "0.025", - "0.025", - "0.05", - "0.025", - "0.025", - "1.0", - "0.05", - "0.025", - "0.025", - "0.025", - "0.025" - ] - ] - } - } - ] - } - } -} diff --git a/packages/grafana-e2e/cypress/fixtures/long-trace-response.json b/packages/grafana-e2e/cypress/fixtures/long-trace-response.json deleted file mode 100644 index 80955535638..00000000000 --- a/packages/grafana-e2e/cypress/fixtures/long-trace-response.json +++ /dev/null @@ -1,7592 +0,0 @@ -{ - "data": [ - { - "traceID": "3fa414edcef6ad90", - "spans": [ - { - "traceID": "3fa414edcef6ad90", - "spanID": "1b26effbab24e95a", - "operationName": "FindTraceByID", - "references": [], - "startTime": 1605873894680581, - "duration": 1820, - "tags": [ - { "key": "component", "type": "string", "value": "gRPC" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0f5c1808567e4403", - "operationName": "FindTraceByID", - "references": [], - "startTime": 1605873894680587, - "duration": 1847, - "tags": [ - { "key": "component", "type": "string", "value": "gRPC" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "59f093577238d61e", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683862, - "duration": 10204, - "tags": [], - "logs": [ - { "timestamp": 1605873894683872, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894694063, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1cc731490b1da4c5", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683858, - "duration": 10257, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "602204dc8b8fbc6d", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683201, - "duration": 11185, - "tags": [], - "logs": [ - { "timestamp": 1605873894683207, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894694385, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "586e5e4c0400de11", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683196, - "duration": 11200, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "779ac3811ce65e40", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683844, - "duration": 10983, - "tags": [ - { "key": "blockID", "type": "string", "value": "20a16df1-a312-4b1a-a2e2-33b55e9f3c8b" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894694822, - "fields": [ - { "key": "bytes", "type": "int64", "value": 315664 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "24203526fe09b1e2", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682997, - "duration": 12453, - "tags": [], - "logs": [ - { "timestamp": 1605873894683002, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894695448, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0afe9ad5f5b01be7", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682993, - "duration": 12466, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "51413d67348a4624", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682986, - "duration": 13059, - "tags": [ - { "key": "blockID", "type": "string", "value": "08b90b09-c56e-4b4a-b95f-3f0409dc9ce9" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894695963, - "fields": [ - { "key": "bytes", "type": "int64", "value": 239824 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "60007a76ffde4644", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682866, - "duration": 13279, - "tags": [], - "logs": [ - { "timestamp": 1605873894682872, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894696144, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "09d7a8c1faef5a84", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682861, - "duration": 13291, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2755efbbfb1b537b", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682846, - "duration": 14054, - "tags": [ - { "key": "blockID", "type": "string", "value": "f78b0397-d3ad-4514-9bf4-87b6ea7e920e" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894696898, - "fields": [ - { "key": "bytes", "type": "int64", "value": 218440 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "25223420e121413a", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683188, - "duration": 14278, - "tags": [ - { "key": "blockID", "type": "string", "value": "3ae22086-9266-481a-9725-c921471e4a94" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894697462, - "fields": [ - { "key": "bytes", "type": "int64", "value": 397880 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "17a3baf85848a727", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683030, - "duration": 14724, - "tags": [], - "logs": [ - { "timestamp": 1605873894683033, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894697752, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "46ebfa6c443776c4", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683027, - "duration": 14734, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "19b1afe02cf639cf", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683883, - "duration": 14279, - "tags": [], - "logs": [ - { "timestamp": 1605873894683889, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894698160, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6e5a7dd55283f907", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683879, - "duration": 14289, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5085badf0c1dc842", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683657, - "duration": 14886, - "tags": [], - "logs": [ - { "timestamp": 1605873894683663, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894698542, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "71a0e94722b662ed", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683653, - "duration": 14897, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "57e69d8f17b39563", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683388, - "duration": 15548, - "tags": [], - "logs": [ - { "timestamp": 1605873894683394, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894698936, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6fe636103f47e1fc", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683384, - "duration": 15558, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "52146a5c1b2c0030", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683284, - "duration": 15701, - "tags": [], - "logs": [ - { "timestamp": 1605873894683290, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894698984, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "160fb4c8329a2ea0", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683280, - "duration": 15712, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1e283fe0dd8cc773", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683024, - "duration": 16029, - "tags": [ - { "key": "blockID", "type": "string", "value": "9e102b4e-115a-4bda-abd6-aa6221f9e4b7" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894699050, - "fields": [ - { "key": "bytes", "type": "int64", "value": 395808 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1bae5c35dd7187ba", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683644, - "duration": 15612, - "tags": [ - { "key": "blockID", "type": "string", "value": "b2f5a951-19a0-473d-8830-e1120ab7bf25" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894699255, - "fields": [ - { "key": "bytes", "type": "int64", "value": 345992 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5af2c497b60703d9", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683842, - "duration": 15628, - "tags": [], - "logs": [ - { "timestamp": 1605873894683848, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894699469, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6a64d382dd0239a7", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683837, - "duration": 15639, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "04652166eaec115c", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683378, - "duration": 16179, - "tags": [ - { "key": "blockID", "type": "string", "value": "30903640-5e8c-4cf6-9dc8-f84e0e2541c8" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894699555, - "fields": [ - { "key": "bytes", "type": "int64", "value": 291056 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "650c7f5ec8cc53a5", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683871, - "duration": 15807, - "tags": [ - { "key": "blockID", "type": "string", "value": "19b49abb-e17a-4632-a4b9-3ce95208e3cf" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894699675, - "fields": [ - { "key": "bytes", "type": "int64", "value": 424248 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1b30323ce39314b9", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684553, - "duration": 15144, - "tags": [], - "logs": [ - { "timestamp": 1605873894684559, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894699696, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "288816ad36c9020c", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684549, - "duration": 15154, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "26e83a54365218ad", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683881, - "duration": 16602, - "tags": [], - "logs": [ - { "timestamp": 1605873894683888, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894700482, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5e6a2e62081720fd", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683878, - "duration": 16613, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "63332243ceed106c", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683893, - "duration": 16666, - "tags": [], - "logs": [ - { "timestamp": 1605873894683900, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894700557, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7dbbbda52a6d32ce", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683888, - "duration": 16678, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "195ed27075e44238", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683828, - "duration": 16766, - "tags": [ - { "key": "blockID", "type": "string", "value": "6c5d1290-2b4b-4f33-9798-63b6654e16b4" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894700591, - "fields": [ - { "key": "bytes", "type": "int64", "value": 367848 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "35e5a12a53c6088a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682748, - "duration": 17901, - "tags": [], - "logs": [ - { "timestamp": 1605873894682751, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894700647, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "690fcd8c8dc87ae8", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683273, - "duration": 17376, - "tags": [ - { "key": "blockID", "type": "string", "value": "f1db0c64-befe-4790-af19-7b48e57a9558" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894700646, - "fields": [ - { "key": "bytes", "type": "int64", "value": 386448 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "113befce4abfecb2", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682745, - "duration": 17911, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "277870fa55872b13", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683865, - "duration": 17440, - "tags": [ - { "key": "blockID", "type": "string", "value": "f05f1d13-0250-492a-abc8-bca24ccf3a15" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894701291, - "fields": [ - { "key": "bytes", "type": "int64", "value": 211672 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "022b6c95374f166d", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683879, - "duration": 17471, - "tags": [ - { "key": "blockID", "type": "string", "value": "0cba7eaf-2546-41ac-99d7-673ef23d6e98" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894701347, - "fields": [ - { "key": "bytes", "type": "int64", "value": 406456 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6cee3530fc730d34", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684631, - "duration": 16733, - "tags": [], - "logs": [ - { "timestamp": 1605873894684639, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894701363, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "674b435291a256c4", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684627, - "duration": 16745, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1de85b574e5d906c", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683373, - "duration": 18328, - "tags": [], - "logs": [ - { "timestamp": 1605873894683380, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894701701, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4c5ac8757f9888b7", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683369, - "duration": 18338, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3e5ab83b57207c74", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683042, - "duration": 18823, - "tags": [], - "logs": [ - { "timestamp": 1605873894683045, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894701863, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7d9927e5c258d511", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682730, - "duration": 19136, - "tags": [ - { "key": "blockID", "type": "string", "value": "794e2adc-701e-4c2d-907a-66221b4455d3" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894701864, - "fields": [ - { "key": "bytes", "type": "int64", "value": 289928 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6c9178ed1e68f858", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683039, - "duration": 18834, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "445d4f3f2dc4d0ad", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684214, - "duration": 17919, - "tags": [], - "logs": [ - { "timestamp": 1605873894684221, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894702132, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "30dd998b2082f2b9", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684210, - "duration": 17958, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2ff9bbb6c991a0ea", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683877, - "duration": 18301, - "tags": [], - "logs": [ - { "timestamp": 1605873894683883, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894702177, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "310a2399bb07e8bd", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683873, - "duration": 18311, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "19021bbbe6310785", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683360, - "duration": 18978, - "tags": [ - { "key": "blockID", "type": "string", "value": "d2212e62-5b1a-41e2-ae43-c0a596125f1b" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894702335, - "fields": [ - { "key": "bytes", "type": "int64", "value": 199208 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "021f72c9979124b5", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684621, - "duration": 17816, - "tags": [ - { "key": "blockID", "type": "string", "value": "06ebaf3b-4501-4cda-91fb-c48a9d33a99c" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894702434, - "fields": [ - { "key": "bytes", "type": "int64", "value": 384696 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "68a1e78424019eb9", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683657, - "duration": 18900, - "tags": [], - "logs": [ - { "timestamp": 1605873894683663, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894702556, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "244e73561d0c691d", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683653, - "duration": 18910, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5c1d1b2d38dddcfb", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683518, - "duration": 19222, - "tags": [], - "logs": [ - { "timestamp": 1605873894683526, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894702739, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "364583eecf36b543", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683513, - "duration": 19232, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "22e42286de359dc4", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683843, - "duration": 18969, - "tags": [], - "logs": [ - { "timestamp": 1605873894683849, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894702812, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7b936283fac4d0ac", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683838, - "duration": 18981, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "660886869edd36cf", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684202, - "duration": 18627, - "tags": [ - { "key": "blockID", "type": "string", "value": "f07137b8-7a0b-4199-b1a7-6b7d5b230723" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894702824, - "fields": [ - { "key": "bytes", "type": "int64", "value": 293936 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "57ed8902af3a60b5", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683549, - "duration": 19426, - "tags": [], - "logs": [ - { "timestamp": 1605873894683554, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894702972, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "64cadcdb4f18b2f7", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683544, - "duration": 19437, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "25f434fb5960aaef", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683933, - "duration": 19303, - "tags": [], - "logs": [ - { "timestamp": 1605873894683939, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894703235, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "62afac560d435620", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683929, - "duration": 19314, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "58435ec74d79cc93", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683865, - "duration": 19469, - "tags": [ - { "key": "blockID", "type": "string", "value": "f2a53e6e-e261-4ec2-92bd-97c5a4c4b760" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894703331, - "fields": [ - { "key": "bytes", "type": "int64", "value": 390648 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "578849d0d44400b5", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684004, - "duration": 19335, - "tags": [], - "logs": [ - { "timestamp": 1605873894684012, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894703337, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1f5faebfb90378ad", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683999, - "duration": 19346, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "41f1eb48b61ef185", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683035, - "duration": 20463, - "tags": [ - { "key": "blockID", "type": "string", "value": "941a63d4-2739-4ba2-9a15-08256b5c9eae" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894703490, - "fields": [ - { "key": "bytes", "type": "int64", "value": 438128 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "71ee8c7b83046da0", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683645, - "duration": 19895, - "tags": [ - { "key": "blockID", "type": "string", "value": "151c489c-a86a-49b7-9fa9-31d1714d59ee" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894703538, - "fields": [ - { "key": "bytes", "type": "int64", "value": 325344 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1bf030a07aaceb80", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683218, - "duration": 20692, - "tags": [], - "logs": [ - { "timestamp": 1605873894683225, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894703909, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "54b34afd73af12d1", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683215, - "duration": 21011, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6a7ba0261825c53c", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683505, - "duration": 20615, - "tags": [ - { "key": "blockID", "type": "string", "value": "db0fa030-4607-40e5-998b-47029aa3430e" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894704118, - "fields": [ - { "key": "bytes", "type": "int64", "value": 411272 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2a597269b23b1bcb", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683426, - "duration": 20720, - "tags": [], - "logs": [ - { "timestamp": 1605873894683431, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894704146, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "66d886579510b6fd", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683422, - "duration": 20841, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1ef8e63340342174", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683990, - "duration": 20334, - "tags": [ - { "key": "blockID", "type": "string", "value": "a10ec85d-9fd2-403e-abcd-6f4ec49b0396" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894704322, - "fields": [ - { "key": "bytes", "type": "int64", "value": 442360 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "46c6de90778460b1", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683830, - "duration": 20675, - "tags": [ - { "key": "blockID", "type": "string", "value": "7e9e0142-15ff-461e-8e05-6c62d920603a" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894704502, - "fields": [ - { "key": "bytes", "type": "int64", "value": 465744 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "686f3e58fe28940f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894696113, - "duration": 8480, - "tags": [], - "logs": [ - { "timestamp": 1605873894696126, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894704591, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5960c1f5750b1cde", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894696104, - "duration": 8495, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6aa5ddd42d96f825", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683921, - "duration": 20886, - "tags": [ - { "key": "blockID", "type": "string", "value": "43e5ad4f-11d6-4f25-9925-652cb801fd58" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894704804, - "fields": [ - { "key": "bytes", "type": "int64", "value": 405344 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "166377800e8e82a7", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683214, - "duration": 21686, - "tags": [], - "logs": [ - { "timestamp": 1605873894683221, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894704899, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5e84f8676ef1efad", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683209, - "duration": 21696, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "713c834576a0d9b0", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683037, - "duration": 22197, - "tags": [], - "logs": [ - { "timestamp": 1605873894683045, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894705234, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "209c0e336c71e932", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683033, - "duration": 22209, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1bd34d50efadb568", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683414, - "duration": 21894, - "tags": [ - { "key": "blockID", "type": "string", "value": "b432160f-347c-41ad-882e-f1786e4b42b1" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894705305, - "fields": [ - { "key": "bytes", "type": "int64", "value": 409104 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "58cee6c544e69e4f", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683206, - "duration": 22173, - "tags": [ - { "key": "blockID", "type": "string", "value": "b12afd19-298a-443f-97ce-b5b2e5bc9d79" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894705375, - "fields": [ - { "key": "bytes", "type": "int64", "value": 376872 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4e48e93f70e06522", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894696061, - "duration": 9466, - "tags": [ - { "key": "blockID", "type": "string", "value": "45701f45-c93a-4c35-9fed-9cce2c316a19" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894705524, - "fields": [ - { "key": "bytes", "type": "int64", "value": 453296 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2422bf6c2ed108c2", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683536, - "duration": 20571, - "tags": [ - { "key": "blockID", "type": "string", "value": "51945006-c165-40af-baea-769b3199bf46" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894704104, - "fields": [ - { "key": "bytes", "type": "int64", "value": 342200 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "42fac7c66e0ca970", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683200, - "duration": 22685, - "tags": [ - { "key": "blockID", "type": "string", "value": "8772aa40-3489-4b12-b685-9f708ae4de75" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894705882, - "fields": [ - { "key": "bytes", "type": "int64", "value": 407152 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2a86d93e70a1720c", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684627, - "duration": 21313, - "tags": [], - "logs": [ - { "timestamp": 1605873894684633, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894705939, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "72991150a8c3cf08", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684622, - "duration": 21322, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3ceac51ce73f994e", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683627, - "duration": 22375, - "tags": [], - "logs": [ - { "timestamp": 1605873894683633, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894706001, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "704707012227a4f1", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683623, - "duration": 22386, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "26cf501f6dcbb968", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683959, - "duration": 22090, - "tags": [], - "logs": [ - { "timestamp": 1605873894683965, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894706048, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7ebb1c9d8a55ac56", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683952, - "duration": 22104, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1bd01ea1e13ac6fd", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683733, - "duration": 22571, - "tags": [], - "logs": [ - { "timestamp": 1605873894683739, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894706303, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4f94f7e28081e1af", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683728, - "duration": 22582, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "60fd2b3931676856", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684310, - "duration": 22119, - "tags": [], - "logs": [ - { "timestamp": 1605873894684317, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894706428, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "432bc11447588912", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684305, - "duration": 22131, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1e1aa88072a7cefc", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683026, - "duration": 23483, - "tags": [ - { "key": "blockID", "type": "string", "value": "9064347a-7c49-48d8-b348-8d734f7fd542" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894706506, - "fields": [ - { "key": "bytes", "type": "int64", "value": 365672 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1fb49823a6f803bf", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682930, - "duration": 23695, - "tags": [], - "logs": [ - { "timestamp": 1605873894682935, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894706622, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7db786f0da6d756d", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682926, - "duration": 23705, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "01cb21bacc3933da", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894697497, - "duration": 9150, - "tags": [], - "logs": [ - { "timestamp": 1605873894697507, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894706646, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "260399c49430577a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894697488, - "duration": 9166, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5ba9d86263fc6da1", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684614, - "duration": 22250, - "tags": [ - { "key": "blockID", "type": "string", "value": "ca346cf4-8162-49e5-a0d0-0619d3813794" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894706862, - "fields": [ - { "key": "bytes", "type": "int64", "value": 416472 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "77f27a840cd8b75b", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685061, - "duration": 21838, - "tags": [], - "logs": [ - { "timestamp": 1605873894685068, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894706897, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4a48a86f95e117f9", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685057, - "duration": 21850, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7d1f782957acfe32", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682777, - "duration": 24168, - "tags": [], - "logs": [ - { "timestamp": 1605873894682783, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894706944, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "77f8165c15176536", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682773, - "duration": 24206, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7f20dbc684de78c8", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683615, - "duration": 23703, - "tags": [ - { "key": "blockID", "type": "string", "value": "f518974f-2e1e-41c8-b70c-cd2088f5a081" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894707316, - "fields": [ - { "key": "bytes", "type": "int64", "value": 278728 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "70a453eeff8ec687", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684828, - "duration": 22510, - "tags": [], - "logs": [ - { "timestamp": 1605873894684835, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894707337, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0c7d975a67c6d7bc", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684823, - "duration": 22520, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7ccb153793c6afd9", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683720, - "duration": 23911, - "tags": [ - { "key": "blockID", "type": "string", "value": "6f72b73b-c5fe-4761-b91f-b92f447441fa" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894707629, - "fields": [ - { "key": "bytes", "type": "int64", "value": 451984 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5bf10b9afef405a9", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894697476, - "duration": 10175, - "tags": [ - { "key": "blockID", "type": "string", "value": "61e0a11e-5e88-49c4-ad1d-81636670e642" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894707648, - "fields": [ - { "key": "bytes", "type": "int64", "value": 296328 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1aae38562e2b6a1f", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683945, - "duration": 23883, - "tags": [ - { "key": "blockID", "type": "string", "value": "7dda9580-666b-42f9-b8a1-1680a0de352f" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894707823, - "fields": [ - { "key": "bytes", "type": "int64", "value": 402936 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1dc5a0697b5d6161", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682764, - "duration": 25091, - "tags": [ - { "key": "blockID", "type": "string", "value": "bf101e70-4a86-4d88-890c-e976330ba857" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894707853, - "fields": [ - { "key": "bytes", "type": "int64", "value": 385288 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3df0c4e2de834172", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684425, - "duration": 23557, - "tags": [], - "logs": [ - { "timestamp": 1605873894684432, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894707980, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "50f5d53109a047da", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684421, - "duration": 23568, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "64e62db2206bdda3", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682595, - "duration": 25553, - "tags": [], - "logs": [ - { "timestamp": 1605873894682603, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894708147, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "40f0742ab8be92ab", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682589, - "duration": 25564, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6b89efb6b9fb16fc", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684815, - "duration": 23341, - "tags": [ - { "key": "blockID", "type": "string", "value": "84b0a7ea-895d-49f9-892c-11f689f0c13f" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894708154, - "fields": [ - { "key": "bytes", "type": "int64", "value": 424816 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "33c05fda4c7d3921", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684414, - "duration": 23815, - "tags": [], - "logs": [ - { "timestamp": 1605873894684420, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894708228, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "742995638b3636e6", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684409, - "duration": 23825, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1cf9294062a5780b", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682920, - "duration": 25427, - "tags": [ - { "key": "blockID", "type": "string", "value": "e43ee3db-63c9-4d2b-a791-99a5a9203e4e" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894708341, - "fields": [ - { "key": "bytes", "type": "int64", "value": 396312 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6852631d2c6d1586", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683843, - "duration": 24695, - "tags": [], - "logs": [ - { "timestamp": 1605873894683850, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894708538, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1691ee4e1f907b39", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683839, - "duration": 24706, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1bcd55e85df0601a", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684400, - "duration": 24493, - "tags": [ - { "key": "blockID", "type": "string", "value": "211313f2-7284-43eb-b9dc-134b5b344524" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894708891, - "fields": [ - { "key": "bytes", "type": "int64", "value": 249032 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7757c670662153b5", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682578, - "duration": 26605, - "tags": [ - { "key": "blockID", "type": "string", "value": "99a8b127-bef6-4718-997b-18e5cb6bee81" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894709180, - "fields": [ - { "key": "bytes", "type": "int64", "value": 443904 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "033e809d9deb02fb", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683129, - "duration": 26149, - "tags": [], - "logs": [ - { "timestamp": 1605873894683139, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894709277, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0701e7633d141024", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683124, - "duration": 26160, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5a1fcbfa2c2e077e", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682990, - "duration": 26296, - "tags": [], - "logs": [ - { "timestamp": 1605873894682993, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894709285, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2639318a16168a94", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682987, - "duration": 26304, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "573267e2aab9eb37", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683831, - "duration": 25627, - "tags": [ - { "key": "blockID", "type": "string", "value": "9a5df823-d980-4671-b33f-ef92e485232f" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894709455, - "fields": [ - { "key": "bytes", "type": "int64", "value": 357872 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3705123c90491605", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683886, - "duration": 25575, - "tags": [], - "logs": [ - { "timestamp": 1605873894683890, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894709460, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "46138581a74be710", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683883, - "duration": 25585, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "369cd4694f877602", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684325, - "duration": 25173, - "tags": [], - "logs": [ - { "timestamp": 1605873894684385, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894709498, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7aab906468c79c5b", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894703359, - "duration": 6145, - "tags": [], - "logs": [ - { "timestamp": 1605873894703375, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894709503, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "57f0ffddbcc40049", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684321, - "duration": 25185, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0c27a77ad2f6bbb3", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894703354, - "duration": 6155, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "27f360a42e423410", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894696933, - "duration": 12666, - "tags": [], - "logs": [ - { "timestamp": 1605873894696942, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894709598, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7e5086a8bb3eb3b3", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894696926, - "duration": 12678, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "42f4a2e45bc6b552", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683602, - "duration": 26169, - "tags": [], - "logs": [ - { "timestamp": 1605873894683608, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894709771, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "693c3e7a4e085ce6", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683598, - "duration": 26180, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7645427b1d8ca012", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894694874, - "duration": 15023, - "tags": [], - "logs": [ - { "timestamp": 1605873894694896, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894709897, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2e6e130f1e7bf5ca", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894694866, - "duration": 15038, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2155087a44565c8a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894700685, - "duration": 9446, - "tags": [], - "logs": [ - { "timestamp": 1605873894700698, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894710131, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "66ed873b2793ee77", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894700678, - "duration": 9459, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "69654d80ac69ec92", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894702020, - "duration": 8202, - "tags": [], - "logs": [ - { "timestamp": 1605873894702031, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894710221, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3a0447242878ba00", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894701338, - "duration": 8890, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2e73b563bfa4df76", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683106, - "duration": 27358, - "tags": [ - { "key": "blockID", "type": "string", "value": "b89a056f-d8cd-41e9-84ad-445e68d0a0d5" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894710462, - "fields": [ - { "key": "bytes", "type": "int64", "value": 337664 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2e958ff5d95860cf", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683591, - "duration": 27357, - "tags": [ - { "key": "blockID", "type": "string", "value": "4767ecb2-01d3-450b-b005-6b9219fdfd71" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894710945, - "fields": [ - { "key": "bytes", "type": "int64", "value": 421576 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4854f2803a2439d0", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684313, - "duration": 26669, - "tags": [ - { "key": "blockID", "type": "string", "value": "ec9c982f-485f-47b2-be74-a7f203368ede" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894710972, - "fields": [ - { "key": "bytes", "type": "int64", "value": 409016 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "62aa1124fbaafe29", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684107, - "duration": 26934, - "tags": [], - "logs": [ - { "timestamp": 1605873894684113, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894711040, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "67ee705c301e7e2a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684102, - "duration": 26945, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "417798c3fbab4244", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894696911, - "duration": 14252, - "tags": [ - { "key": "blockID", "type": "string", "value": "6a346739-04b1-4e86-8f87-e182b01cf5cd" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894711160, - "fields": [ - { "key": "bytes", "type": "int64", "value": 407144 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "17faaf92fbea2ed9", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683730, - "duration": 27707, - "tags": [], - "logs": [ - { "timestamp": 1605873894683736, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894711435, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "29d4e2aa59eae59e", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683726, - "duration": 27719, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3b9a85f6cd6075b8", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894701327, - "duration": 10230, - "tags": [ - { "key": "blockID", "type": "string", "value": "ece056d3-aa27-464b-81a8-643b3ae208e4" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894711554, - "fields": [ - { "key": "bytes", "type": "int64", "value": 359960 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0da2897c85659567", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683377, - "duration": 28594, - "tags": [], - "logs": [ - { "timestamp": 1605873894683384, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894711971, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4407d391acba81fc", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683373, - "duration": 28605, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1987773829521f8f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894699105, - "duration": 12885, - "tags": [], - "logs": [ - { "timestamp": 1605873894699119, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894711989, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1c9553a6471269c6", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894699099, - "duration": 12896, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3dedf220c1f51d38", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894704853, - "duration": 7356, - "tags": [], - "logs": [ - { "timestamp": 1605873894704879, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894712209, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "25820f0eebf05ab3", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894704846, - "duration": 7370, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7027388faf7e1bf1", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684937, - "duration": 27418, - "tags": [], - "logs": [ - { "timestamp": 1605873894684943, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894712354, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "44c6d6c7e1afb67d", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684933, - "duration": 27429, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3f654e75b41629f5", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683718, - "duration": 28677, - "tags": [ - { "key": "blockID", "type": "string", "value": "0e5b1fbb-ab10-44b7-89a0-f8932ee26dcf" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894712392, - "fields": [ - { "key": "bytes", "type": "int64", "value": 369000 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4fa1d1a031112ab0", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682996, - "duration": 29565, - "tags": [], - "logs": [ - { "timestamp": 1605873894683006, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894712560, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2e0985a0b4168ff2", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682988, - "duration": 29577, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7a7bf32e81f4317e", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682984, - "duration": 29753, - "tags": [ - { "key": "blockID", "type": "string", "value": "c56f4809-bc48-4f81-9656-a3bbb96ba87e" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894712734, - "fields": [ - { "key": "bytes", "type": "int64", "value": 406296 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "66d4f363dfa46bdb", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684041, - "duration": 28730, - "tags": [], - "logs": [ - { "timestamp": 1605873894684111, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894712771, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "616b800031f78e5f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684037, - "duration": 28741, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5b0d3da4dac0a4ab", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683364, - "duration": 29595, - "tags": [ - { "key": "blockID", "type": "string", "value": "10e42379-1c35-419e-a26c-2630b9d2cdd2" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894712956, - "fields": [ - { "key": "bytes", "type": "int64", "value": 354744 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1571e420dca57b9f", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683878, - "duration": 29122, - "tags": [ - { "key": "blockID", "type": "string", "value": "adb287c7-69e4-4ed8-8604-c3302c766db2" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894712996, - "fields": [ - { "key": "bytes", "type": "int64", "value": 300104 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3120fb610c52c9a6", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684415, - "duration": 28590, - "tags": [ - { "key": "blockID", "type": "string", "value": "faebcb3d-444a-4675-8e55-2f46dbcaa1d7" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894713002, - "fields": [ - { "key": "bytes", "type": "int64", "value": 410672 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "46feff0edeabb674", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683135, - "duration": 29700, - "tags": [], - "logs": [ - { "timestamp": 1605873894683141, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894712834, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "32df737f09cd2bf9", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683131, - "duration": 29904, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "604de25c9811a395", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894699064, - "duration": 14141, - "tags": [ - { "key": "blockID", "type": "string", "value": "7df8ef23-0902-4b4b-92aa-b6c1aeb3c9c2" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894713203, - "fields": [ - { "key": "bytes", "type": "int64", "value": 436328 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7867c14538ff0c61", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684923, - "duration": 28333, - "tags": [ - { "key": "blockID", "type": "string", "value": "a94e5162-7e01-4bd6-b5c8-1bc3b50c67c6" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894713253, - "fields": [ - { "key": "bytes", "type": "int64", "value": 433064 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "04e793f4b075b20f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684557, - "duration": 28822, - "tags": [], - "logs": [ - { "timestamp": 1605873894684565, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894713378, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3084a10a11a62355", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684553, - "duration": 28832, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0a0b86e5738d630b", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685048, - "duration": 28355, - "tags": [ - { "key": "blockID", "type": "string", "value": "36ce4c95-0cb6-4803-bdfd-b316b3c0cc4c" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894713400, - "fields": [ - { "key": "bytes", "type": "int64", "value": 293136 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "62ea00c2c871a91e", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894704821, - "duration": 8702, - "tags": [ - { "key": "blockID", "type": "string", "value": "b9c0dc2b-ee12-4876-a517-2902c6fe655e" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894713521, - "fields": [ - { "key": "bytes", "type": "int64", "value": 415912 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0f54c2d4ac7df141", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683769, - "duration": 29774, - "tags": [], - "logs": [ - { "timestamp": 1605873894683775, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894713542, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5e650633f1c4cb45", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683764, - "duration": 29784, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4cff4ebd296d36f0", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684028, - "duration": 29524, - "tags": [ - { "key": "blockID", "type": "string", "value": "e6200492-f24a-40ef-946a-e89170d1ac54" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894713549, - "fields": [ - { "key": "bytes", "type": "int64", "value": 447592 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0da82a874696fec5", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684230, - "duration": 29351, - "tags": [], - "logs": [ - { "timestamp": 1605873894684236, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894713581, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "20334815e0eb1b97", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684226, - "duration": 29362, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "20de4a897b30c066", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683315, - "duration": 30412, - "tags": [], - "logs": [ - { "timestamp": 1605873894683323, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894713726, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6edcc31aa4c96617", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683309, - "duration": 30424, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3594272577366bc9", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684491, - "duration": 29257, - "tags": [], - "logs": [ - { "timestamp": 1605873894684498, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894713747, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "01e9f897c4145c38", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684485, - "duration": 29270, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "53e0bef2bbb77bea", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684901, - "duration": 28911, - "tags": [], - "logs": [ - { "timestamp": 1605873894684908, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894713812, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3df7804d8e682193", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684897, - "duration": 28919, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5664530667612f1f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682860, - "duration": 31015, - "tags": [], - "logs": [ - { "timestamp": 1605873894682871, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894713875, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4b6340b15001f8c8", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682855, - "duration": 31026, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6b3d3f0643735e5f", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894694842, - "duration": 19190, - "tags": [ - { "key": "blockID", "type": "string", "value": "f0b87e56-00a6-4270-8ce0-b47affb9113e" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894714027, - "fields": [ - { "key": "bytes", "type": "int64", "value": 310320 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4a4b3e0d2f115bcf", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683987, - "duration": 30363, - "tags": [], - "logs": [ - { "timestamp": 1605873894683994, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894714350, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1e0da3179b38449d", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683983, - "duration": 30374, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "318fcd8e3bfc42c7", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684209, - "duration": 30152, - "tags": [], - "logs": [ - { "timestamp": 1605873894684215, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894714360, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "37e82ddc44e6bf60", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684205, - "duration": 30162, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0271272ae09aac5f", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684542, - "duration": 29977, - "tags": [ - { "key": "blockID", "type": "string", "value": "bfbb9652-84f8-4145-8091-8197ea922ad3" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894714514, - "fields": [ - { "key": "bytes", "type": "int64", "value": 432848 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2d80feb23cbbb7cd", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684508, - "duration": 30161, - "tags": [], - "logs": [ - { "timestamp": 1605873894684515, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894714668, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "01ad9e5d3837c5b6", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684503, - "duration": 30172, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "42698e68a26de8cf", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683756, - "duration": 30942, - "tags": [ - { "key": "blockID", "type": "string", "value": "55f71d63-05b0-4c3a-b79f-a2563307bf40" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894714695, - "fields": [ - { "key": "bytes", "type": "int64", "value": 402624 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6ea302c343fec88f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683645, - "duration": 31247, - "tags": [], - "logs": [ - { "timestamp": 1605873894683652, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894714891, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "279e17d93d4978da", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683641, - "duration": 31258, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "59b29ac1ab225873", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683001, - "duration": 31930, - "tags": [], - "logs": [ - { "timestamp": 1605873894683006, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894714930, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4196b1f250632b3e", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682998, - "duration": 31938, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "224b550ee6ad2bf2", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684889, - "duration": 30088, - "tags": [ - { "key": "blockID", "type": "string", "value": "07baec6a-187a-493b-b160-772936b5a3f0" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894714974, - "fields": [ - { "key": "bytes", "type": "int64", "value": 429360 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "368bcd97b5e9dde0", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684678, - "duration": 30934, - "tags": [], - "logs": [ - { "timestamp": 1605873894684685, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894715611, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3d88bddf112b8ae2", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684672, - "duration": 30946, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "718a103bd19501b2", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684196, - "duration": 31424, - "tags": [ - { "key": "blockID", "type": "string", "value": "a85669d8-148b-4d61-a359-8f97c036b880" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894715617, - "fields": [ - { "key": "bytes", "type": "int64", "value": 401280 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2b56997697dd91c0", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684472, - "duration": 31151, - "tags": [ - { "key": "blockID", "type": "string", "value": "75911f2c-fc5e-4ef1-bcff-9abad2120f23" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894715621, - "fields": [ - { "key": "bytes", "type": "int64", "value": 397808 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1c049cad7edf280e", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683630, - "duration": 32119, - "tags": [ - { "key": "blockID", "type": "string", "value": "fdcc5380-c15f-41c2-9a34-623d6cdd2d5a" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894715733, - "fields": [ - { "key": "bytes", "type": "int64", "value": 397464 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6e3d16e8ed14d90c", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894702877, - "duration": 12975, - "tags": [], - "logs": [ - { "timestamp": 1605873894702894, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894715852, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7bd595782cdb70c3", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894699700, - "duration": 16154, - "tags": [], - "logs": [ - { "timestamp": 1605873894699708, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894715853, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "08a9d074d520a512", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894702864, - "duration": 12993, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "083316368540b811", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894699695, - "duration": 16164, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7f067cadc2b4569d", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684535, - "duration": 31520, - "tags": [], - "logs": [ - { "timestamp": 1605873894684540, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894716053, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5061bd596bc8a7e7", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684531, - "duration": 31530, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4b9772650994e725", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682990, - "duration": 33209, - "tags": [ - { "key": "blockID", "type": "string", "value": "61022db6-4401-40b6-a3a2-1f4cd5ccb430" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894716196, - "fields": [ - { "key": "bytes", "type": "int64", "value": 380504 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0e9c6b89215308ba", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683975, - "duration": 32340, - "tags": [ - { "key": "blockID", "type": "string", "value": "f17c848f-2f99-4215-a5e9-1f55d8e15c1e" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894716311, - "fields": [ - { "key": "bytes", "type": "int64", "value": 447736 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "030573bc0520e3c2", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683061, - "duration": 33348, - "tags": [], - "logs": [ - { "timestamp": 1605873894683067, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894716409, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0d2e16a8cf201e5a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683057, - "duration": 33357, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0361f359be22f9c8", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894702474, - "duration": 14135, - "tags": [], - "logs": [ - { "timestamp": 1605873894702483, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894716607, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5310c5c355550cad", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894702463, - "duration": 14156, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "16870d24920c25b8", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894699686, - "duration": 17072, - "tags": [ - { "key": "blockID", "type": "string", "value": "320a9ecd-a9fd-4c88-8aeb-e8a312dcce0c" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894716753, - "fields": [ - { "key": "bytes", "type": "int64", "value": 424544 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "62090e9e1c22bb56", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707676, - "duration": 9095, - "tags": [], - "logs": [ - { "timestamp": 1605873894707691, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894716771, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "02d91deb1ff0ea76", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707670, - "duration": 9107, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0cc47cc1eb5deb29", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894702844, - "duration": 13991, - "tags": [ - { "key": "blockID", "type": "string", "value": "04e21143-53ef-4083-948e-3bbe502c2d44" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894716832, - "fields": [ - { "key": "bytes", "type": "int64", "value": 401728 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1b27a749f4d1b557", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684297, - "duration": 32550, - "tags": [ - { "key": "blockID", "type": "string", "value": "d1ffbf86-0e11-4b6e-b9ae-8466e7c42a90" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894716844, - "fields": [ - { "key": "bytes", "type": "int64", "value": 193992 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3c5f2282a3e7c658", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683345, - "duration": 33584, - "tags": [], - "logs": [ - { "timestamp": 1605873894683352, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894716927, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6fc62f7a1ae1a6e9", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683340, - "duration": 33596, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "524a9c941765266a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684584, - "duration": 32449, - "tags": [], - "logs": [ - { "timestamp": 1605873894684592, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894717030, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5bcaa6a4a1c06160", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684579, - "duration": 32460, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2d4e045a72c17ff2", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684465, - "duration": 32666, - "tags": [ - { "key": "blockID", "type": "string", "value": "4c9f58b7-b944-4692-9d5b-14270bd1b8d6" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894717127, - "fields": [ - { "key": "bytes", "type": "int64", "value": 436224 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "25548a46750dfecb", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684652, - "duration": 32684, - "tags": [ - { "key": "blockID", "type": "string", "value": "ffd8fb66-db97-4451-9a97-bfb6631b82a5" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894717332, - "fields": [ - { "key": "bytes", "type": "int64", "value": 434536 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5f4913a50dcd37c8", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683050, - "duration": 34487, - "tags": [ - { "key": "blockID", "type": "string", "value": "0255db6b-061e-4ceb-9ae9-598588995be8" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894717533, - "fields": [ - { "key": "bytes", "type": "int64", "value": 392520 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6750f7ac4a5b50e8", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684648, - "duration": 32974, - "tags": [], - "logs": [ - { "timestamp": 1605873894684654, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894717621, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "41f4b72bd0a14291", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684644, - "duration": 32984, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "03fac2f4c91b31b6", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894702448, - "duration": 15285, - "tags": [ - { "key": "blockID", "type": "string", "value": "f7da9248-f02e-44df-b243-c1f5f69e0f67" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894717730, - "fields": [ - { "key": "bytes", "type": "int64", "value": 366872 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4934a16eeec96b0d", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684005, - "duration": 33827, - "tags": [], - "logs": [ - { "timestamp": 1605873894684010, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894717831, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0ebf8034f9944320", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684001, - "duration": 33836, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0cc1f6dfcc153616", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683672, - "duration": 34388, - "tags": [], - "logs": [ - { "timestamp": 1605873894683679, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894718059, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "501e4211325ef503", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683667, - "duration": 34399, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7bcf0390730028ef", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683332, - "duration": 34943, - "tags": [ - { "key": "blockID", "type": "string", "value": "cbeb7cd1-c8b1-4290-be74-4caf7b3f2d69" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894718272, - "fields": [ - { "key": "bytes", "type": "int64", "value": 432424 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1b30f12cd1728ebf", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683995, - "duration": 34418, - "tags": [ - { "key": "blockID", "type": "string", "value": "2dd90b29-ffb5-4a27-bcd7-0950ca151c14" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894718411, - "fields": [ - { "key": "bytes", "type": "int64", "value": 210280 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "22a3f914c23d3456", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684570, - "duration": 34106, - "tags": [ - { "key": "blockID", "type": "string", "value": "4fca87b1-ceb1-4290-a3cb-c0a970a7c5a6" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894718671, - "fields": [ - { "key": "bytes", "type": "int64", "value": 422528 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "169359b95c501fae", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683035, - "duration": 35830, - "tags": [], - "logs": [ - { "timestamp": 1605873894683041, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894718864, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "71541fab4a38308f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683031, - "duration": 35841, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3a7fc15a2fb60753", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707659, - "duration": 11315, - "tags": [ - { "key": "blockID", "type": "string", "value": "57b3be9d-2234-4b8b-a380-424f30717e5b" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894718970, - "fields": [ - { "key": "bytes", "type": "int64", "value": 437088 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "10e57e001b6c6127", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683210, - "duration": 35772, - "tags": [], - "logs": [ - { "timestamp": 1605873894683220, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894718980, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2c3fb8ad983d67fc", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683206, - "duration": 35784, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3fca8d21c0827061", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684366, - "duration": 34674, - "tags": [], - "logs": [ - { "timestamp": 1605873894684372, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894719039, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "25a226515c2f9150", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684362, - "duration": 34687, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5fb9111ac6a5d18d", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683274, - "duration": 35965, - "tags": [], - "logs": [ - { "timestamp": 1605873894683286, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894719238, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6945097dfeae216a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683268, - "duration": 35979, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6dd256468dea419f", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684628, - "duration": 34986, - "tags": [ - { "key": "blockID", "type": "string", "value": "0a5b8e26-05d5-4df2-97c1-a57ccb631b5e" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894719610, - "fields": [ - { "key": "bytes", "type": "int64", "value": 368392 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "22c3bb99916b1cf9", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684954, - "duration": 34724, - "tags": [], - "logs": [ - { "timestamp": 1605873894684961, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894719678, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7786d37aacb34302", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684949, - "duration": 34735, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0b4489a19011e658", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894702395, - "duration": 17621, - "tags": [], - "logs": [ - { "timestamp": 1605873894702404, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894720015, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5fe309dbb10a8aa0", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894702385, - "duration": 17639, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "28a88c33b44009c0", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684502, - "duration": 35545, - "tags": [], - "logs": [ - { "timestamp": 1605873894684508, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894720047, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6d20c9fb0d7d023a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683045, - "duration": 37003, - "tags": [], - "logs": [ - { "timestamp": 1605873894683057, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894720047, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7aed634e79451eff", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684353, - "duration": 35695, - "tags": [ - { "key": "blockID", "type": "string", "value": "c4b4a484-2704-49e8-926b-e7bb7a13c520" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894720046, - "fields": [ - { "key": "bytes", "type": "int64", "value": 394640 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2361a627270177b2", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684498, - "duration": 35556, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6365c636dea9cf69", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683040, - "duration": 37014, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7fd1af0b8e4b13e5", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894704467, - "duration": 15587, - "tags": [], - "logs": [ - { "timestamp": 1605873894704477, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894720054, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4d0b05c2fe988374", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894704458, - "duration": 15600, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "45d91fa92cb81841", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683191, - "duration": 36908, - "tags": [ - { "key": "blockID", "type": "string", "value": "abec5c1e-02b5-4165-8b3d-2940d3adb991" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894720066, - "fields": [ - { "key": "bytes", "type": "int64", "value": 352392 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4287af315802d4cd", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894704355, - "duration": 15863, - "tags": [], - "logs": [ - { "timestamp": 1605873894704369, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894720218, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "62b352c305041dcc", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894704348, - "duration": 15876, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6ec165f264482f57", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683346, - "duration": 37139, - "tags": [], - "logs": [ - { "timestamp": 1605873894683351, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894720484, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7400527184eeef19", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683342, - "duration": 37152, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": ["invalid parent span IDs=4ff7c150586c7e6f; skipping clock skew adjustment"] - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1561e391ecd756d5", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684939, - "duration": 35696, - "tags": [ - { "key": "blockID", "type": "string", "value": "7dbea947-c624-454c-a99a-b2aa0c96c19f" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894720631, - "fields": [ - { "key": "bytes", "type": "int64", "value": 328592 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "57f916fcf19f117f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682643, - "duration": 38011, - "tags": [], - "logs": [ - { "timestamp": 1605873894682653, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894720650, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1d4458304925bc0f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682638, - "duration": 38028, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": ["invalid parent span IDs=3ff0fd3a1cdb9b5e; skipping clock skew adjustment"] - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6f251bfe2c45ae12", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894709481, - "duration": 11241, - "tags": [], - "logs": [ - { "timestamp": 1605873894709489, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894720722, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5365877f5f1070a3", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894709476, - "duration": 11253, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": ["invalid parent span IDs=5d1a0e533881c649; skipping clock skew adjustment"] - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5472390246aac5c4", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683265, - "duration": 37540, - "tags": [ - { "key": "blockID", "type": "string", "value": "31cd1597-b435-467c-8726-9fd43cb8f75a" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894720802, - "fields": [ - { "key": "bytes", "type": "int64", "value": 263520 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "38b14977915ca22c", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683471, - "duration": 37385, - "tags": [], - "logs": [ - { "timestamp": 1605873894683477, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894720855, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "08ecb88049158355", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683466, - "duration": 37394, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": ["invalid parent span IDs=241c721a4337e64b; skipping clock skew adjustment"] - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4cb042697154defa", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684490, - "duration": 36497, - "tags": [ - { "key": "blockID", "type": "string", "value": "37430ec1-eb84-4ad4-9bea-64b05bc05f0b" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894720984, - "fields": [ - { "key": "bytes", "type": "int64", "value": 329440 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "01afdbfe975f8d6d", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894704258, - "duration": 16738, - "tags": [ - { "key": "blockID", "type": "string", "value": "52136585-3c3c-418c-85bb-079c46f30ee8" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894720994, - "fields": [ - { "key": "bytes", "type": "int64", "value": 271408 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1c881037e38b18ad", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894704334, - "duration": 16710, - "tags": [ - { "key": "blockID", "type": "string", "value": "293f4ca9-60cf-4dce-84f9-90d7a8903467" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894721042, - "fields": [ - { "key": "bytes", "type": "int64", "value": 448208 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "727cf2a7b14f8891", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683209, - "duration": 37913, - "tags": [], - "logs": [ - { "timestamp": 1605873894683217, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894721121, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "72a3d0dd535ed714", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683196, - "duration": 37928, - "tags": [], - "logs": [ - { "timestamp": 1605873894683202, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894721124, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6e28aae41ed950a0", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683204, - "duration": 37923, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1034cca4b87566b9", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683192, - "duration": 37937, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5477c4334a555c1a", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894702369, - "duration": 18817, - "tags": [ - { "key": "blockID", "type": "string", "value": "65c35f00-7bf4-4d7c-884e-57e1f4f386f1" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894721184, - "fields": [ - { "key": "bytes", "type": "int64", "value": 343160 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "156254fce90fef6d", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683261, - "duration": 38071, - "tags": [ - { "key": "blockID", "type": "string", "value": "5f6da848-1f43-4327-b791-c8607c834469" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894721329, - "fields": [ - { "key": "bytes", "type": "int64", "value": 425008 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "18174ee576735b69", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683380, - "duration": 38087, - "tags": [], - "logs": [ - { "timestamp": 1605873894683386, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894721466, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3c984a418432da06", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683376, - "duration": 38097, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2cb4a90ec9e7ed56", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684207, - "duration": 37304, - "tags": [ - { "key": "blockID", "type": "string", "value": "da15aeab-47f3-4150-a3a0-0b899f5728e0" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894721505, - "fields": [ - { "key": "bytes", "type": "int64", "value": 435448 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "31a678641a0daa14", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683185, - "duration": 38722, - "tags": [ - { "key": "blockID", "type": "string", "value": "7f859498-9292-4be9-9902-9cc0cc94db7f" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894721902, - "fields": [ - { "key": "bytes", "type": "int64", "value": 382184 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "27ca437dde2b9612", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683192, - "duration": 38914, - "tags": [ - { "key": "blockID", "type": "string", "value": "723cdf42-e4bc-48dc-bda5-6173eb15dee4" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894722104, - "fields": [ - { "key": "bytes", "type": "int64", "value": 374272 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "777ba94dbf7e2679", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894703342, - "duration": 18950, - "tags": [ - { "key": "blockID", "type": "string", "value": "9ffb7568-b253-46bf-ae30-275a5370abdd" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894722288, - "fields": [ - { "key": "bytes", "type": "int64", "value": 296800 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "447a1de4607678e0", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894716862, - "duration": 5567, - "tags": [], - "logs": [ - { "timestamp": 1605873894716881, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894722428, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4a1cb35fb165238f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894716854, - "duration": 5582, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "263d88ba7760646a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707662, - "duration": 14938, - "tags": [], - "logs": [ - { "timestamp": 1605873894707677, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894722599, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7cb0a9332c646221", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707654, - "duration": 14951, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "178dbf7349f30deb", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682835, - "duration": 39846, - "tags": [ - { "key": "blockID", "type": "string", "value": "ae4b5b30-d87d-459f-8bfe-d05f4f169ced" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894722679, - "fields": [ - { "key": "bytes", "type": "int64", "value": 384344 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "150994409f1cb25a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894700618, - "duration": 22118, - "tags": [], - "logs": [ - { "timestamp": 1605873894700633, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894722736, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "585e5d65d550b215", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894700613, - "duration": 22129, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "27511615066e34db", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684102, - "duration": 38879, - "tags": [], - "logs": [ - { "timestamp": 1605873894684108, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894722980, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "79b947d9ed7866a6", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684097, - "duration": 38891, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6f2e6507fe12975c", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894717161, - "duration": 5887, - "tags": [], - "logs": [ - { "timestamp": 1605873894717174, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894723047, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "00cdeb6a7479c53f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894717155, - "duration": 5899, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "24bddd4ea3487e35", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683371, - "duration": 39804, - "tags": [ - { "key": "blockID", "type": "string", "value": "d7601ebc-c33c-492c-a1da-4aa053533084" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894723171, - "fields": [ - { "key": "bytes", "type": "int64", "value": 365144 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "53d7bccf2fc103c5", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894716842, - "duration": 6390, - "tags": [ - { "key": "blockID", "type": "string", "value": "ca64f28a-77dc-4745-abdc-44054c1e5e40" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894723228, - "fields": [ - { "key": "bytes", "type": "int64", "value": 289352 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "552db884462abcc8", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683762, - "duration": 39518, - "tags": [], - "logs": [ - { "timestamp": 1605873894683768, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894723279, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "66bfd77009ceabee", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683756, - "duration": 39531, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "39ecc86ead7ef908", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684607, - "duration": 38759, - "tags": [], - "logs": [ - { "timestamp": 1605873894684613, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894723365, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "23e63f9ee6638cc5", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684602, - "duration": 38769, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3995f8a937161d18", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685603, - "duration": 37861, - "tags": [], - "logs": [ - { "timestamp": 1605873894685611, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894723463, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7d8cbf547f13bab8", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685598, - "duration": 37871, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "50ea9bcb501f096f", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707639, - "duration": 15983, - "tags": [ - { "key": "blockID", "type": "string", "value": "7ba16a23-7c29-4c4e-a0a6-f5a35697f61e" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894723607, - "fields": [ - { "key": "bytes", "type": "int64", "value": 283976 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "36174ad72177e7e0", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683065, - "duration": 40713, - "tags": [], - "logs": [ - { "timestamp": 1605873894683103, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894723777, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6eb33f7265438984", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683062, - "duration": 40722, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0d173415b42b54df", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684351, - "duration": 39618, - "tags": [], - "logs": [ - { "timestamp": 1605873894684357, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894723968, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0353b10977450cf7", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684346, - "duration": 39628, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7b094f4ebdce31c3", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894717141, - "duration": 6985, - "tags": [ - { "key": "blockID", "type": "string", "value": "f1a4a13f-5e5d-484e-8b53-1f4f8e1bad37" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894724124, - "fields": [ - { "key": "bytes", "type": "int64", "value": 448472 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4bf98f62683b0e8e", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894700602, - "duration": 23557, - "tags": [ - { "key": "blockID", "type": "string", "value": "2c8d9e08-28c9-43e0-a38e-48e205e70c0a" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894724156, - "fields": [ - { "key": "bytes", "type": "int64", "value": 454976 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3d32ea43a7ace6a3", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685122, - "duration": 39094, - "tags": [], - "logs": [ - { "timestamp": 1605873894685153, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894724214, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "42780d9e0c2beb80", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685114, - "duration": 39108, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6d4dfd6622f9d4e5", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684594, - "duration": 39780, - "tags": [ - { "key": "blockID", "type": "string", "value": "6926ecb7-efff-41c5-ae95-85e0b8e75bed" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894724372, - "fields": [ - { "key": "bytes", "type": "int64", "value": 364000 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "462c7cc77e9bde26", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684089, - "duration": 40384, - "tags": [ - { "key": "blockID", "type": "string", "value": "eacc5319-5c66-4ef0-bdc7-61ebcd665770" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894724469, - "fields": [ - { "key": "bytes", "type": "int64", "value": 375312 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "64c5e8cb8a8c9c87", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684553, - "duration": 40087, - "tags": [], - "logs": [ - { "timestamp": 1605873894684559, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894724639, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6a2abf3fa1e44a02", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684548, - "duration": 40098, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5185d47ca37c94b2", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684289, - "duration": 40372, - "tags": [], - "logs": [ - { "timestamp": 1605873894684296, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894724661, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "63f3f66dc1b96cb5", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684283, - "duration": 40383, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "09dfedf04619fd00", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683527, - "duration": 41169, - "tags": [], - "logs": [ - { "timestamp": 1605873894683532, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894724695, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4fcf6b895ba07eb1", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683524, - "duration": 41178, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1d3e7eff78cb43af", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684135, - "duration": 40664, - "tags": [], - "logs": [ - { "timestamp": 1605873894684142, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894724800, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "43860f9f193430f0", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684131, - "duration": 40675, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7ea1f5a8b4dab8a7", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684338, - "duration": 40775, - "tags": [ - { "key": "blockID", "type": "string", "value": "2906f33b-d748-4827-8eb7-de90927a65dd" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894725108, - "fields": [ - { "key": "bytes", "type": "int64", "value": 431856 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "512c60978bd2eac4", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894721019, - "duration": 4095, - "tags": [], - "logs": [ - { "timestamp": 1605873894721028, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894725112, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7e897df0e96d32b5", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894721010, - "duration": 4112, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "787b23c8fa301dd7", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894706900, - "duration": 18234, - "tags": [], - "logs": [ - { "timestamp": 1605873894706927, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894725133, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3ffd9ecc1161334a", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683055, - "duration": 42085, - "tags": [ - { "key": "blockID", "type": "string", "value": "26f1bad8-cd58-4801-8785-d52b8d833a90" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894725137, - "fields": [ - { "key": "bytes", "type": "int64", "value": 414056 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2f86e3ff470976d3", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894706887, - "duration": 18254, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1a42c28bcd21acc8", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685104, - "duration": 40144, - "tags": [ - { "key": "blockID", "type": "string", "value": "777d1eb8-cd33-44d5-8e34-2d253bd948a6" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894725246, - "fields": [ - { "key": "bytes", "type": "int64", "value": 407792 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "445f67ce7c86e918", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684540, - "duration": 40905, - "tags": [ - { "key": "blockID", "type": "string", "value": "d4b28adc-eb54-4e54-8639-40acbe82196a" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894725443, - "fields": [ - { "key": "bytes", "type": "int64", "value": 312232 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4dc9df94476d41ef", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894713432, - "duration": 12062, - "tags": [], - "logs": [ - { "timestamp": 1605873894713448, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894725366, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1f47248f05173a34", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894713423, - "duration": 12078, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6ab47177b7f5e532", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684151, - "duration": 41357, - "tags": [], - "logs": [ - { "timestamp": 1605873894684157, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894725507, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "62088478a235ead5", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684146, - "duration": 41368, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "021658e91c35b26e", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683518, - "duration": 42121, - "tags": [ - { "key": "blockID", "type": "string", "value": "6deff928-b65a-437a-8d56-64d2397d9d1f" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894725636, - "fields": [ - { "key": "bytes", "type": "int64", "value": 238936 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "48401abd95ffa153", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894712421, - "duration": 13359, - "tags": [], - "logs": [ - { "timestamp": 1605873894712431, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894725779, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6a6bcedc4fc18a61", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894712416, - "duration": 13371, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "551f266c080ab0c6", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684122, - "duration": 41711, - "tags": [ - { "key": "blockID", "type": "string", "value": "5180765b-50b6-4de3-abab-ceea6086afb8" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894725830, - "fields": [ - { "key": "bytes", "type": "int64", "value": 393392 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "72970316c65770af", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894706872, - "duration": 18990, - "tags": [ - { "key": "blockID", "type": "string", "value": "541a6a31-d25a-4275-9688-228355c81085" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894725860, - "fields": [ - { "key": "bytes", "type": "int64", "value": 280296 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "635a7471f4256c0b", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684275, - "duration": 41695, - "tags": [ - { "key": "blockID", "type": "string", "value": "6bf57585-a03d-44e1-bd18-081b679d3e4a" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894725967, - "fields": [ - { "key": "bytes", "type": "int64", "value": 434432 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7607fc837fc26251", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683802, - "duration": 42223, - "tags": [], - "logs": [ - { "timestamp": 1605873894683808, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894726025, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "01269c3f9d50434a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683798, - "duration": 42233, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "70114fe92b16120e", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894721062, - "duration": 5033, - "tags": [], - "logs": [ - { "timestamp": 1605873894721068, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894726093, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "37a69429cff69860", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894721058, - "duration": 5043, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5262951e45efa67d", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894720997, - "duration": 5110, - "tags": [ - { "key": "blockID", "type": "string", "value": "5c09e7bd-2f9c-42d3-8d2f-863e93eaa939" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894726103, - "fields": [ - { "key": "bytes", "type": "int64", "value": 246840 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1d6a76dd2ca6c3e6", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894711590, - "duration": 14746, - "tags": [], - "logs": [ - { "timestamp": 1605873894711600, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894726335, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7c75cd286737359f", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894711582, - "duration": 14759, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "78415d3812916d77", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707879, - "duration": 18497, - "tags": [], - "logs": [ - { "timestamp": 1605873894707887, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894726375, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5f7ac8c4fd5c680a", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707874, - "duration": 18513, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6b4bc2ee6a63726e", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894713413, - "duration": 13261, - "tags": [ - { "key": "blockID", "type": "string", "value": "b654e510-386a-470a-98c4-fb9833d21728" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894726671, - "fields": [ - { "key": "bytes", "type": "int64", "value": 384056 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5f55f469fc2d6d29", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894712403, - "duration": 14367, - "tags": [ - { "key": "blockID", "type": "string", "value": "3fca3f89-a174-460d-9a87-92ed03555497" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894726767, - "fields": [ - { "key": "bytes", "type": "int64", "value": 334896 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "19235cb1f2dccb32", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894711567, - "duration": 15276, - "tags": [ - { "key": "blockID", "type": "string", "value": "de228107-4ff6-449e-9f1f-6ab34765ab68" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894726841, - "fields": [ - { "key": "bytes", "type": "int64", "value": 225832 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "29b186beff361524", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894699284, - "duration": 27562, - "tags": [], - "logs": [ - { "timestamp": 1605873894699298, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894726845, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0acce3e2af327bae", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894699278, - "duration": 27578, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6b343c544d82bb3b", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894721050, - "duration": 5994, - "tags": [ - { "key": "blockID", "type": "string", "value": "459be32f-b91d-491b-aa61-5389b239eed8" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894727041, - "fields": [ - { "key": "bytes", "type": "int64", "value": 429792 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "62e3ccbe325de3d1", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683746, - "duration": 43412, - "tags": [ - { "key": "blockID", "type": "string", "value": "a1b8740a-6430-4113-93f4-ad9c52e42d62" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894727155, - "fields": [ - { "key": "bytes", "type": "int64", "value": 366096 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "310178852fbf88e0", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682731, - "duration": 44481, - "tags": [], - "logs": [ - { "timestamp": 1605873894682741, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894727211, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "42bb5e9919ea40f4", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682726, - "duration": 44493, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "150c8cfeedab6a38", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894708915, - "duration": 18374, - "tags": [], - "logs": [ - { "timestamp": 1605873894708922, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894727288, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1dda89d441503741", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894708910, - "duration": 18384, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6c1c11a742626433", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707863, - "duration": 19558, - "tags": [ - { "key": "blockID", "type": "string", "value": "ef68962f-224d-4b6c-9dcd-ef9be8607c72" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894727419, - "fields": [ - { "key": "bytes", "type": "int64", "value": 423912 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4393a9fa65c8ceae", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707853, - "duration": 19639, - "tags": [], - "logs": [ - { "timestamp": 1605873894707866, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894727491, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1392dcdbb07bc781", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707848, - "duration": 19650, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0804f1e82c8828e2", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894713028, - "duration": 14650, - "tags": [], - "logs": [ - { "timestamp": 1605873894713040, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894727677, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4e9e2ce15a6e596c", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894713023, - "duration": 14661, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0c763a5ef614a2f4", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685588, - "duration": 42409, - "tags": [ - { "key": "blockID", "type": "string", "value": "65fbe578-d535-4032-a12f-f249e7405363" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894727993, - "fields": [ - { "key": "bytes", "type": "int64", "value": 441088 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "31c1f2fd1bde2d49", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682863, - "duration": 45441, - "tags": [], - "logs": [ - { "timestamp": 1605873894682870, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894728303, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2a694924a7a45244", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682710, - "duration": 45639, - "tags": [ - { "key": "blockID", "type": "string", "value": "06a1301e-5b48-425e-a506-a4350afe3d0d" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894728346, - "fields": [ - { "key": "bytes", "type": "int64", "value": 418168 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "73f7696fdccac589", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682859, - "duration": 45516, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4b52acf382a86008", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684137, - "duration": 44241, - "tags": [ - { "key": "blockID", "type": "string", "value": "57df43b4-8552-49e3-a1cd-f442609deaf2" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894728373, - "fields": [ - { "key": "bytes", "type": "int64", "value": 425104 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "18688fac379c4a9e", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894707836, - "duration": 20550, - "tags": [ - { "key": "blockID", "type": "string", "value": "c53e7db4-34cd-43d0-9383-de5e40936182" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894728383, - "fields": [ - { "key": "bytes", "type": "int64", "value": 389928 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5fccf30d8fc010b8", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894726139, - "duration": 2327, - "tags": [], - "logs": [ - { "timestamp": 1605873894726153, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894728465, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1c2bf57fc386ac18", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894726130, - "duration": 2343, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7cfea87d3c8d06ca", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685457, - "duration": 43096, - "tags": [], - "logs": [ - { "timestamp": 1605873894685465, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894728552, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "12a80c97c2a42f05", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685452, - "duration": 43107, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "08122694d5e37cb7", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894723200, - "duration": 5455, - "tags": [], - "logs": [ - { "timestamp": 1605873894723209, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894728654, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "313e6147867246d6", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894723194, - "duration": 5466, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2e4f73b3315eb612", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894715649, - "duration": 13049, - "tags": [], - "logs": [ - { "timestamp": 1605873894715658, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894728698, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7e0457958022516b", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894715644, - "duration": 13060, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "04db9d1320bc1720", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683309, - "duration": 45454, - "tags": [], - "logs": [ - { "timestamp": 1605873894683316, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894728762, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4702ca2057b120a0", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894726116, - "duration": 2652, - "tags": [ - { "key": "blockID", "type": "string", "value": "ec5b73d8-61f8-4210-8838-e6cfd253294b" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894728766, - "fields": [ - { "key": "bytes", "type": "int64", "value": 173264 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5e2c536cf48b42a8", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683305, - "duration": 45464, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "69bd1c5d5184626b", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894708900, - "duration": 19981, - "tags": [ - { "key": "blockID", "type": "string", "value": "de324468-b889-4f4c-af77-907353a719cd" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894728878, - "fields": [ - { "key": "bytes", "type": "int64", "value": 307032 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6849c669006c2759", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894714727, - "duration": 14480, - "tags": [], - "logs": [ - { "timestamp": 1605873894714736, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894729206, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6836249c56ca87ae", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894714722, - "duration": 14490, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4614a3c3374430a7", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894682850, - "duration": 46413, - "tags": [ - { "key": "blockID", "type": "string", "value": "ea416fc3-eedc-413e-9cc1-d8fd3548cfe6" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894729260, - "fields": [ - { "key": "bytes", "type": "int64", "value": 392160 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "33a5b0766e98269c", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894685441, - "duration": 44269, - "tags": [ - { "key": "blockID", "type": "string", "value": "b50c3305-8d80-42fc-88a4-97a8604c7066" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894729705, - "fields": [ - { "key": "bytes", "type": "int64", "value": 346072 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "38da2ce44e352b40", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894722577, - "duration": 7233, - "tags": [], - "logs": [ - { "timestamp": 1605873894722588, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894729809, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2a0fc54146d07c21", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894722569, - "duration": 7247, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "398ed8e3573cf781", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894715632, - "duration": 14249, - "tags": [ - { "key": "blockID", "type": "string", "value": "2d9f964a-aac5-42e1-b410-866ad1706d7a" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894729879, - "fields": [ - { "key": "bytes", "type": "int64", "value": 441600 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "47c93569ac9ecd04", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894713013, - "duration": 16911, - "tags": [ - { "key": "blockID", "type": "string", "value": "96c1b791-af15-4554-a1bb-b0f200625856" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894729919, - "fields": [ - { "key": "bytes", "type": "int64", "value": 446504 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3d61171be03f3db4", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894714709, - "duration": 15261, - "tags": [ - { "key": "blockID", "type": "string", "value": "ae957436-40cc-4d15-a3c3-26d10613aaf6" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894729967, - "fields": [ - { "key": "bytes", "type": "int64", "value": 287976 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3de17f2475734d79", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894723182, - "duration": 6796, - "tags": [ - { "key": "blockID", "type": "string", "value": "eb218dd8-99fa-4de7-87cd-8998b89e0778" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894729976, - "fields": [ - { "key": "bytes", "type": "int64", "value": 403400 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "712c995480f17232", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894713402, - "duration": 16771, - "tags": [], - "logs": [ - { "timestamp": 1605873894713412, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894730172, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "26950bc4bf84c34b", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894713392, - "duration": 16787, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "1bd3c2e5acbea9c4", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894701374, - "duration": 29019, - "tags": [], - "logs": [ - { "timestamp": 1605873894701383, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894730393, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "2106e0853647ac08", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894701369, - "duration": 29030, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6e59a327558a7329", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894699265, - "duration": 31183, - "tags": [ - { "key": "blockID", "type": "string", "value": "d8b5fee2-e2b3-445c-8ea9-243519a5c104" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894730443, - "fields": [ - { "key": "bytes", "type": "int64", "value": 400224 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "037acce8385b1858", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894721017, - "duration": 9605, - "tags": [], - "logs": [ - { "timestamp": 1605873894721028, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894730621, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3547a2504168d8c7", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894721012, - "duration": 9617, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "0dc60016513590c8", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894722299, - "duration": 8583, - "tags": [ - { "key": "blockID", "type": "string", "value": "69120461-fbd8-4ca0-a5fb-957d745a22a8" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894730879, - "fields": [ - { "key": "bytes", "type": "int64", "value": 370472 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5d18bf1b78bd4e75", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684396, - "duration": 46590, - "tags": [], - "logs": [ - { "timestamp": 1605873894684402, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894730985, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3bdc5547104db28c", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894684392, - "duration": 46600, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "682e6d017ce71dad", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894713268, - "duration": 17796, - "tags": [ - { "key": "blockID", "type": "string", "value": "84644a06-da08-4c3a-936f-70a634d8052d" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894731057, - "fields": [ - { "key": "bytes", "type": "int64", "value": 353808 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3bbbdf937ccb2ccb", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894721935, - "duration": 9258, - "tags": [], - "logs": [ - { "timestamp": 1605873894721944, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894731193, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7c40bff5b749a532", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894721928, - "duration": 9272, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "31f86900e7598e55", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894701358, - "duration": 29885, - "tags": [ - { "key": "blockID", "type": "string", "value": "186f31e1-409b-4c9c-95b5-abc662389d3b" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894731240, - "fields": [ - { "key": "bytes", "type": "int64", "value": 434496 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "7471b3c5a188e8da", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894729995, - "duration": 1327, - "tags": [], - "logs": [ - { "timestamp": 1605873894730003, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894731321, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "33cb5e55876f584c", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894729989, - "duration": 1338, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "58b7d0b41550e88f", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894729978, - "duration": 1396, - "tags": [ - { "key": "blockID", "type": "string", "value": "e0d8f1ac-a48f-4dea-868f-183e1a124fb5" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894731373, - "fields": [ - { "key": "bytes", "type": "int64", "value": 16488 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "70405f4198f01d16", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894721544, - "duration": 9893, - "tags": [], - "logs": [ - { "timestamp": 1605873894721555, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894731436, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "56f0f0d7120ea5a5", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894721540, - "duration": 9903, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "69bb9bf8c37d9faf", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894683022, - "duration": 48646, - "tags": [ - { "key": "blockID", "type": "string", "value": "cf7747f5-68f9-490b-840d-9975c057c7e6" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894731663, - "fields": [ - { "key": "bytes", "type": "int64", "value": 425640 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "4ef61721dfd7f61b", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894720840, - "duration": 10841, - "tags": [], - "logs": [ - { "timestamp": 1605873894720869, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894731680, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "70e8aa6d13e56007", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894720827, - "duration": 10860, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "297ff96c736c18f4", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894705333, - "duration": 26573, - "tags": [], - "logs": [ - { "timestamp": 1605873894705342, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894731904, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6a80209f067be4f5", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894705328, - "duration": 26585, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "5b3db530e83db855", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894731272, - "duration": 796, - "tags": [], - "logs": [ - { "timestamp": 1605873894731284, "fields": [{ "key": "keys requested", "type": "int64", "value": 1 }] }, - { "timestamp": 1605873894732067, "fields": [{ "key": "keys found", "type": "int64", "value": 1 }] } - ], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "6b2458a9486a4298", - "operationName": "Memcache.GetMulti", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894731265, - "duration": 886, - "tags": [ - { "key": "organization", "type": "string", "value": "1" }, - { "key": "span.kind", "type": "string", "value": "client" } - ], - "logs": [], - "processID": "p1", - "warnings": null - }, - { - "traceID": "3fa414edcef6ad90", - "spanID": "3139145bb422702e", - "operationName": "block.Find", - "references": [{ "refType": "CHILD_OF", "traceID": "3fa414edcef6ad90", "spanID": "1b26effbab24e95a" }], - "startTime": 1605873894731251, - "duration": 941, - "tags": [ - { "key": "blockID", "type": "string", "value": "f9fd03ba-91a8-476b-b809-d2b11bfa790d" }, - { "key": "shardKey", "type": "int64", "value": 5 } - ], - "logs": [ - { - "timestamp": 1605873894732190, - "fields": [ - { "key": "bytes", "type": "int64", "value": 8680 }, - { "key": "msg", "type": "string", "value": "bloom" } - ] - } - ], - "processID": "p1", - "warnings": null - } - ], - "processes": { - "p1": { - "serviceName": "s1" - } - }, - "warnings": null - } - ], - "total": 0, - "limit": 0, - "offset": 0, - "errors": null -} diff --git a/packages/grafana-e2e/cypress/fixtures/prometheus-query-range-response.json b/packages/grafana-e2e/cypress/fixtures/prometheus-query-range-response.json deleted file mode 100644 index 6a2501a45df..00000000000 --- a/packages/grafana-e2e/cypress/fixtures/prometheus-query-range-response.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "status": "success", - "data": { - "resultType": "matrix", - "result": [ - { - "metric": {}, - "values": [ - [1620758235, "0.07554431352019486"], - [1620758250, "0.0756695553961457"], - [1620758265, "0.0757369945411682"], - [1620758280, "0.07560212035898113"], - [1620758295, "0.07556358506832812"], - [1620758310, "0.07558766859344893"], - [1620758325, "0.07552022996976834"], - [1620758340, "0.07553949807996531"], - [1620758355, "0.07554913414209416"], - [1620758370, "0.07539017545449077"], - [1620758385, "0.07524566527721041"], - [1620758400, "0.06631294924007665"], - [1620758415, "0.020769530989205368"], - [1620758430, "0.05720168751283235"], - [1620758445, "0.07271760187022697"], - [1620758460, "0.07282398348834057"], - [1620758475, "0.07272243619599422"], - [1620758490, "0.0727659581600079"], - [1620758505, "0.07290135207155769"], - [1620758520, "0.07293036876672591"], - [1620758535, "0.0727901374111541"], - [1620758550, "0.07272727333735175"], - [1620758565, "0.07264506733699574"], - [1620758580, "0.07272243607717656"], - [1620758595, "0.0728288184987238"], - [1620758610, "0.07298839709448537"], - [1620758625, "0.07301257421338406"], - [1620758640, "0.07304158515671498"], - [1620758655, "0.07311895518980911"], - [1620758670, "0.07325918868870857"], - [1620758685, "0.07340909025275498"], - [1620758700, "0.06640878600261439"], - [1620758715, "0.016943481796378928"], - [1620758730, "0.009846410786372045"], - [1620758745, "0.009846533933076818"], - [1620758760, "0.009865643995544734"], - [1620758775, "0.009877495333796778"], - [1620758790, "0.009894557340703772"], - [1620758805, "0.0098843910341446"], - [1620758820, "0.00990408341969324"], - [1620758835, "0.00989844441243741"], - [1620758850, "0.009889907575638773"], - [1620758865, "0.009918898761738633"], - [1620758880, "0.009937127911002756"], - [1620758895, "0.009940908363410796"], - [1620758910, "0.00998103477604732"], - [1620758925, "0.009972785096318881"], - [1620758940, "0.012851280416358784"], - [1620758955, "0.016073228821362785"], - [1620758970, "0.020414802032173343"], - [1620761580, "0.007599075245347286"], - [1620761595, "0.008931710803442608"], - [1620761610, "0.008726716914241494"], - [1620761625, "0.008200081743024097"], - [1620761640, "0.00855242238708798"], - [1620761655, "0.008286349295644651"], - [1620761670, "0.008226278261449314"], - [1620761685, "0.008195191146355274"], - [1620761700, "0.008187372718523614"], - [1620761715, "0.008513095070485845"], - [1620761730, "0.08239661322810221"], - [1620761745, "0.0859446307478243"], - [1620761760, "0.08307358128715034"], - [1620761775, "0.08068720480328369"], - [1620761790, "0.07619009806120529"], - [1620761805, "0.0750613052160521"], - [1620761820, "0.07146092807229597"], - [1620761835, "0.06898128960085806"] - ] - } - ] - } -} diff --git a/packages/grafana-e2e/cypress/fixtures/prometheus-query-response.json b/packages/grafana-e2e/cypress/fixtures/prometheus-query-response.json deleted file mode 100644 index 56df1d882b5..00000000000 --- a/packages/grafana-e2e/cypress/fixtures/prometheus-query-response.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": "success", - "data": { "resultType": "vector", "result": [{ "metric": {}, "value": [1620761849, "0.06765848222986065"] }] } -} diff --git a/packages/grafana-e2e/cypress/fixtures/tempo-response.json b/packages/grafana-e2e/cypress/fixtures/tempo-response.json deleted file mode 100644 index 30136a226a5..00000000000 --- a/packages/grafana-e2e/cypress/fixtures/tempo-response.json +++ /dev/null @@ -1,1181 +0,0 @@ -{ - "results": { - "A": { - "frames": [ - { - "schema": { - "name": "Trace", - "refId": "A", - "meta": { - "preferredVisualisationType": "trace" - }, - "fields": [ - { - "name": "traceID", - "type": "string", - "typeInfo": { - "frame": "string" - } - }, - { - "name": "spanID", - "type": "string", - "typeInfo": { - "frame": "string" - } - }, - { - "name": "parentSpanID", - "type": "string", - "typeInfo": { - "frame": "string" - } - }, - { - "name": "operationName", - "type": "string", - "typeInfo": { - "frame": "string" - } - }, - { - "name": "serviceName", - "type": "string", - "typeInfo": { - "frame": "string" - } - }, - { - "name": "serviceTags", - "type": "other", - "typeInfo": { - "frame": "other" - } - }, - { - "name": "startTime", - "type": "number", - "typeInfo": { - "frame": "float64" - } - }, - { - "name": "duration", - "type": "number", - "typeInfo": { - "frame": "float64" - } - }, - { - "name": "logs", - "type": "other", - "typeInfo": { - "frame": "other" - } - }, - { - "name": "tags", - "type": "other", - "typeInfo": { - "frame": "other" - } - } - ] - }, - "data": { - "values": [ - [ - "04829080550953998599", - "04829080550953998599", - "04829080550953998599", - "04829080550953998599", - "04829080550953998599", - "04829080550953998599", - "04829080550953998599", - "04829080550953998599", - "04829080550953998599", - "04829080550953998599", - "04829080550953998599" - ], - [ - "4829080550953998599", - "2017565205134657253", - "8342878680821632593", - "1224460974534844837", - "275963539390240345", - "8319197348330196958", - "6652870949263121722", - "607757742690372462", - "4351457163250103362", - "6171925700166476032", - "7554371986156978275" - ], - [ - null, - "4829080550953998599", - "2017565205134657253", - "8342878680821632593", - "1224460974534844837", - "275963539390240345", - "4829080550953998599", - "6652870949263121722", - "607757742690372462", - "4351457163250103362", - "6171925700166476032" - ], - [ - "HTTP Client", - "HTTP POST", - "HTTP POST - post", - "HTTP Client", - "HTTP POST", - "HTTP POST - post", - "HTTP GET", - "HTTP GET - root", - "HTTP Client", - "HTTP GET", - "HTTP GET - root" - ], - ["lb", "lb", "app", "app", "app", "db", "lb", "app", "app", "app", "db"], - [ - [ - { - "value": "lb", - "key": "service.name" - }, - { - "value": "tns/loadgen", - "key": "job" - }, - { - "value": "Jaeger-Go-2.22.1", - "key": "opencensus.exporterversion" - }, - { - "value": "f55ab7e317a6", - "key": "host.name" - }, - { - "value": "172.24.0.8", - "key": "ip" - }, - { - "value": "36042b5ca9f81d60", - "key": "client-uuid" - } - ], - [ - { - "value": "lb", - "key": "service.name" - }, - { - "value": "tns/loadgen", - "key": "job" - }, - { - "value": "Jaeger-Go-2.22.1", - "key": "opencensus.exporterversion" - }, - { - "value": "f55ab7e317a6", - "key": "host.name" - }, - { - "value": "172.24.0.8", - "key": "ip" - }, - { - "value": "36042b5ca9f81d60", - "key": "client-uuid" - } - ], - [ - { - "value": "app", - "key": "service.name" - }, - { - "value": "tns/app", - "key": "job" - }, - { - "value": "Jaeger-Go-2.22.1", - "key": "opencensus.exporterversion" - }, - { - "value": "7945a05e75db", - "key": "host.name" - }, - { - "value": "172.24.0.7", - "key": "ip" - }, - { - "value": "78f645af1e60163d", - "key": "client-uuid" - } - ], - [ - { - "value": "app", - "key": "service.name" - }, - { - "value": "tns/app", - "key": "job" - }, - { - "value": "Jaeger-Go-2.22.1", - "key": "opencensus.exporterversion" - }, - { - "value": "7945a05e75db", - "key": "host.name" - }, - { - "value": "172.24.0.7", - "key": "ip" - }, - { - "value": "78f645af1e60163d", - "key": "client-uuid" - } - ], - [ - { - "value": "app", - "key": "service.name" - }, - { - "value": "tns/app", - "key": "job" - }, - { - "value": "Jaeger-Go-2.22.1", - "key": "opencensus.exporterversion" - }, - { - "value": "7945a05e75db", - "key": "host.name" - }, - { - "value": "172.24.0.7", - "key": "ip" - }, - { - "value": "78f645af1e60163d", - "key": "client-uuid" - } - ], - [ - { - "value": "db", - "key": "service.name" - }, - { - "value": "tns/db", - "key": "job" - }, - { - "value": "Jaeger-Go-2.22.1", - "key": "opencensus.exporterversion" - }, - { - "value": "aae464791221", - "key": "host.name" - }, - { - "value": "172.24.0.2", - "key": "ip" - }, - { - "value": "18b1a6b5278dd643", - "key": "client-uuid" - } - ], - [ - { - "value": "lb", - "key": "service.name" - }, - { - "value": "tns/loadgen", - "key": "job" - }, - { - "value": "Jaeger-Go-2.22.1", - "key": "opencensus.exporterversion" - }, - { - "value": "f55ab7e317a6", - "key": "host.name" - }, - { - "value": "172.24.0.8", - "key": "ip" - }, - { - "value": "36042b5ca9f81d60", - "key": "client-uuid" - } - ], - [ - { - "value": "app", - "key": "service.name" - }, - { - "value": "tns/app", - "key": "job" - }, - { - "value": "Jaeger-Go-2.22.1", - "key": "opencensus.exporterversion" - }, - { - "value": "7945a05e75db", - "key": "host.name" - }, - { - "value": "172.24.0.7", - "key": "ip" - }, - { - "value": "78f645af1e60163d", - "key": "client-uuid" - } - ], - [ - { - "value": "app", - "key": "service.name" - }, - { - "value": "tns/app", - "key": "job" - }, - { - "value": "Jaeger-Go-2.22.1", - "key": "opencensus.exporterversion" - }, - { - "value": "7945a05e75db", - "key": "host.name" - }, - { - "value": "172.24.0.7", - "key": "ip" - }, - { - "value": "78f645af1e60163d", - "key": "client-uuid" - } - ], - [ - { - "value": "app", - "key": "service.name" - }, - { - "value": "tns/app", - "key": "job" - }, - { - "value": "Jaeger-Go-2.22.1", - "key": "opencensus.exporterversion" - }, - { - "value": "7945a05e75db", - "key": "host.name" - }, - { - "value": "172.24.0.7", - "key": "ip" - }, - { - "value": "78f645af1e60163d", - "key": "client-uuid" - } - ], - [ - { - "value": "db", - "key": "service.name" - }, - { - "value": "tns/db", - "key": "job" - }, - { - "value": "Jaeger-Go-2.22.1", - "key": "opencensus.exporterversion" - }, - { - "value": "aae464791221", - "key": "host.name" - }, - { - "value": "172.24.0.2", - "key": "ip" - }, - { - "value": "18b1a6b5278dd643", - "key": "client-uuid" - } - ] - ], - [ - 1620761543730.354, 1620761543730.3628, 1620761543732.044, 1620761543732.163, 1620761543732.191, - 1620761543733.925, 1620761543735.194, 1620761543738.197, 1620761543738.394, 1620761543738.398, - 1620761543744.6929 - ], - [17.826, 4.824, 2.588, 2.403, 2.401, 0.142, 13.266, 11.281, 8.593, 10.87, 0.136], - [ - null, - [ - { - "timestamp": 1620761543730.414, - "fields": [ - { - "value": "GetConn", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543730.475, - "fields": [ - { - "value": "DNSStart", - "key": "event" - }, - { - "value": "app", - "key": "host" - } - ] - }, - { - "timestamp": 1620761543731.1738, - "fields": [ - { - "value": "DNSDone", - "key": "event" - }, - { - "value": "172.24.0.7", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543731.184, - "fields": [ - { - "value": "ConnectStart", - "key": "event" - }, - { - "value": "tcp", - "key": "network" - }, - { - "value": "172.24.0.7: 80", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543731.6228, - "fields": [ - { - "value": "ConnectDone", - "key": "event" - }, - { - "value": "tcp", - "key": "network" - }, - { - "value": "172.24.0.7: 80", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543731.678, - "fields": [ - { - "value": "GotConn", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543731.737, - "fields": [ - { - "value": "WroteHeaders", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543731.742, - "fields": [ - { - "value": "WroteRequest", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543735.001, - "fields": [ - { - "value": "GotFirstResponseByte", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543735.186, - "fields": [ - { - "value": "ClosedBody", - "key": "event" - } - ] - } - ], - null, - null, - [ - { - "timestamp": 1620761543732.209, - "fields": [ - { - "value": "GetConn", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543732.236, - "fields": [ - { - "value": "DNSStart", - "key": "event" - }, - { - "value": "db", - "key": "host" - } - ] - }, - { - "timestamp": 1620761543733.196, - "fields": [ - { - "value": "DNSDone", - "key": "event" - }, - { - "value": "172.24.0.2", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543733.203, - "fields": [ - { - "value": "ConnectStart", - "key": "event" - }, - { - "value": "tcp", - "key": "network" - }, - { - "value": "172.24.0.2: 80", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543733.459, - "fields": [ - { - "value": "ConnectDone", - "key": "event" - }, - { - "value": "tcp", - "key": "network" - }, - { - "value": "172.24.0.2: 80", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543733.549, - "fields": [ - { - "value": "GotConn", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543733.613, - "fields": [ - { - "value": "WroteHeaders", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543733.618, - "fields": [ - { - "value": "WroteRequest", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543734.241, - "fields": [ - { - "value": "GotFirstResponseByte", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543734.592, - "fields": [ - { - "value": "ClosedBody", - "key": "event" - } - ] - } - ], - null, - [ - { - "timestamp": 1620761543735.23, - "fields": [ - { - "value": "GetConn", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543735.277, - "fields": [ - { - "value": "DNSStart", - "key": "event" - }, - { - "value": "app", - "key": "host" - } - ] - }, - { - "timestamp": 1620761543736.9658, - "fields": [ - { - "value": "DNSDone", - "key": "event" - }, - { - "value": "172.24.0.7", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543736.9758, - "fields": [ - { - "value": "ConnectStart", - "key": "event" - }, - { - "value": "tcp", - "key": "network" - }, - { - "value": "172.24.0.7: 80", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543737.705, - "fields": [ - { - "value": "ConnectDone", - "key": "event" - }, - { - "value": "tcp", - "key": "network" - }, - { - "value": "172.24.0.7: 80", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543737.773, - "fields": [ - { - "value": "GotConn", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543737.8098, - "fields": [ - { - "value": "WroteHeaders", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543737.811, - "fields": [ - { - "value": "WroteRequest", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543747.939, - "fields": [ - { - "value": "GotFirstResponseByte", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543748.46, - "fields": [ - { - "value": "ClosedBody", - "key": "event" - } - ] - } - ], - null, - null, - [ - { - "timestamp": 1620761543738.446, - "fields": [ - { - "value": "GetConn", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543738.502, - "fields": [ - { - "value": "DNSStart", - "key": "event" - }, - { - "value": "db", - "key": "host" - } - ] - }, - { - "timestamp": 1620761543743.861, - "fields": [ - { - "value": "DNSDone", - "key": "event" - }, - { - "value": "172.24.0.2", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543743.918, - "fields": [ - { - "value": "ConnectStart", - "key": "event" - }, - { - "value": "tcp", - "key": "network" - }, - { - "value": "172.24.0.2: 80", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543744.228, - "fields": [ - { - "value": "ConnectDone", - "key": "event" - }, - { - "value": "tcp", - "key": "network" - }, - { - "value": "172.24.0.2: 80", - "key": "addr" - } - ] - }, - { - "timestamp": 1620761543744.345, - "fields": [ - { - "value": "GotConn", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543744.414, - "fields": [ - { - "value": "WroteHeaders", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543744.416, - "fields": [ - { - "value": "WroteRequest", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543746.825, - "fields": [ - { - "value": "GotFirstResponseByte", - "key": "event" - } - ] - }, - { - "timestamp": 1620761543749.2668, - "fields": [ - { - "value": "ClosedBody", - "key": "event" - } - ] - } - ], - null - ], - [ - [ - { - "value": "const", - "key": "sampler.type" - }, - { - "value": true, - "key": "sampler.param" - }, - { - "value": 0, - "key": "status.code" - } - ], - [ - { - "value": 302, - "key": "http.status_code" - }, - { - "value": "net/http", - "key": "component" - }, - { - "value": "POST", - "key": "http.method" - }, - { - "value": "app: 80", - "key": "http.url" - }, - { - "value": false, - "key": "net/http.reused" - }, - { - "value": false, - "key": "net/http.was_idle" - }, - { - "value": "client", - "key": "span.kind" - }, - { - "value": 0, - "key": "status.code" - } - ], - [ - { - "value": 302, - "key": "http.status_code" - }, - { - "value": "POST", - "key": "http.method" - }, - { - "value": "/post", - "key": "http.url" - }, - { - "value": "net/http", - "key": "component" - }, - { - "value": "server", - "key": "span.kind" - }, - { - "value": 0, - "key": "status.code" - } - ], - [ - { - "value": 0, - "key": "status.code" - } - ], - [ - { - "value": 204, - "key": "http.status_code" - }, - { - "value": "net/http", - "key": "component" - }, - { - "value": "POST", - "key": "http.method" - }, - { - "value": "db: 80", - "key": "http.url" - }, - { - "value": false, - "key": "net/http.reused" - }, - { - "value": false, - "key": "net/http.was_idle" - }, - { - "value": "client", - "key": "span.kind" - }, - { - "value": 0, - "key": "status.code" - } - ], - [ - { - "value": 204, - "key": "http.status_code" - }, - { - "value": "POST", - "key": "http.method" - }, - { - "value": "/post", - "key": "http.url" - }, - { - "value": "net/http", - "key": "component" - }, - { - "value": "server", - "key": "span.kind" - }, - { - "value": 0, - "key": "status.code" - } - ], - [ - { - "value": 200, - "key": "http.status_code" - }, - { - "value": "net/http", - "key": "component" - }, - { - "value": "GET", - "key": "http.method" - }, - { - "value": "app: 80", - "key": "http.url" - }, - { - "value": false, - "key": "net/http.reused" - }, - { - "value": false, - "key": "net/http.was_idle" - }, - { - "value": "client", - "key": "span.kind" - }, - { - "value": 0, - "key": "status.code" - } - ], - [ - { - "value": 200, - "key": "http.status_code" - }, - { - "value": "GET", - "key": "http.method" - }, - { - "value": "/", - "key": "http.url" - }, - { - "value": "net/http", - "key": "component" - }, - { - "value": "server", - "key": "span.kind" - }, - { - "value": 0, - "key": "status.code" - } - ], - [ - { - "value": 0, - "key": "status.code" - } - ], - [ - { - "value": 200, - "key": "http.status_code" - }, - { - "value": "net/http", - "key": "component" - }, - { - "value": "GET", - "key": "http.method" - }, - { - "value": "db: 80", - "key": "http.url" - }, - { - "value": false, - "key": "net/http.reused" - }, - { - "value": false, - "key": "net/http.was_idle" - }, - { - "value": "client", - "key": "span.kind" - }, - { - "value": 0, - "key": "status.code" - } - ], - [ - { - "value": 200, - "key": "http.status_code" - }, - { - "value": "GET", - "key": "http.method" - }, - { - "value": "/", - "key": "http.url" - }, - { - "value": "net/http", - "key": "component" - }, - { - "value": "server", - "key": "span.kind" - }, - { - "value": 0, - "key": "status.code" - } - ] - ] - ] - } - } - ] - } - } -} diff --git a/packages/grafana-e2e/cypress/plugins/benchmark/CDPDataCollector.ts b/packages/grafana-e2e/cypress/plugins/benchmark/CDPDataCollector.ts deleted file mode 100644 index 76e120a741c..00000000000 --- a/packages/grafana-e2e/cypress/plugins/benchmark/CDPDataCollector.ts +++ /dev/null @@ -1,136 +0,0 @@ -import CDP from 'chrome-remote-interface'; -import ProtocolProxyApi from 'devtools-protocol/types/protocol-proxy-api'; -import { countBy, mean } from 'lodash'; -import Tracelib, { TraceEvent } from 'tracelib'; - -import { CollectedData, DataCollector, DataCollectorName } from './DataCollector'; - -type CDPDataCollectorDeps = { - port: number; -}; - -export class CDPDataCollector implements DataCollector { - private tracingCategories: string[]; - - private state: { - client?: CDP.Client; - tracingPromise?: Promise; - traceEvents: TraceEvent[]; - }; - - constructor(private deps: CDPDataCollectorDeps) { - this.state = this.getDefaultState(); - this.tracingCategories = [ - 'disabled-by-default-v8.cpu_profile', - 'disabled-by-default-v8.cpu_profiler', - 'disabled-by-default-v8.cpu_profiler.hires', - 'disabled-by-default-devtools.timeline.frame', - 'disabled-by-default-devtools.timeline', - 'disabled-by-default-devtools.timeline.inputs', - 'disabled-by-default-devtools.timeline.stack', - 'disabled-by-default-devtools.timeline.invalidationTracking', - 'disabled-by-default-layout_shift.debug', - 'disabled-by-default-cc.debug.scheduler.frames', - 'disabled-by-default-blink.debug.display_lock', - ]; - } - - getName = () => DataCollectorName.CDP; - - private resetState = async () => { - if (this.state.client) { - await this.state.client.close(); - } - this.state = this.getDefaultState(); - }; - - private getDefaultState = () => ({ - traceEvents: [], - }); - - // workaround for type declaration issues in cdp lib - private asApis = ( - client: CDP.Client - ): { - Profiler: ProtocolProxyApi.ProfilerApi; - Page: ProtocolProxyApi.PageApi; - Tracing: ProtocolProxyApi.TracingApi; - } => client; - - private getClientApis = async () => this.asApis(await this.getClient()); - - private getClient = async () => { - if (this.state.client) { - return this.state.client; - } - - const client = await CDP({ port: this.deps.port }); - - const { Profiler, Page } = this.asApis(client); - await Promise.all([Page.enable(), Profiler.enable(), Profiler.setSamplingInterval({ interval: 100 })]); - - this.state.client = client; - - return client; - }; - - start: DataCollector['start'] = async ({ id }) => { - if (this.state.tracingPromise) { - throw new Error(`collection in progress - can't start another one! ${id}`); - } - - const { Tracing, Profiler } = await this.getClientApis(); - - await Promise.all([ - Tracing.start({ - bufferUsageReportingInterval: 1000, - traceConfig: { - includedCategories: this.tracingCategories, - }, - }), - Profiler.start(), - ]); - - Tracing.on('dataCollected', ({ value: events }) => { - this.state.traceEvents.push(...events); - }); - - let resolveFn: (data: CollectedData) => void; - this.state.tracingPromise = new Promise((resolve) => { - resolveFn = resolve; - }); - Tracing.on('tracingComplete', ({ dataLossOccurred }) => { - const t = new Tracelib(this.state.traceEvents); - - const eventCounts = countBy(this.state.traceEvents, (ev) => ev.name); - - const fps = t.getFPS(); - - resolveFn({ - eventCounts, - fps: mean(fps.values), - tracingDataLoss: dataLossOccurred ? 1 : 0, - warnings: t.getWarningCounts(), - }); - }); - }; - - stop: DataCollector['stop'] = async (req) => { - if (!this.state.tracingPromise) { - throw new Error(`collection was never started - there is nothing to stop!`); - } - - const { Tracing, Profiler } = await this.getClientApis(); - - // TODO: capture profiler data - const [, , traceData] = await Promise.all([Profiler.stop(), Tracing.end(), this.state.tracingPromise]); - - await this.resetState(); - - return traceData; - }; - - close: DataCollector['close'] = async () => { - await this.resetState(); - }; -} diff --git a/packages/grafana-e2e/cypress/plugins/benchmark/DataCollector.ts b/packages/grafana-e2e/cypress/plugins/benchmark/DataCollector.ts deleted file mode 100644 index bfff10312e2..00000000000 --- a/packages/grafana-e2e/cypress/plugins/benchmark/DataCollector.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type CollectedData = Record; - -export enum DataCollectorName { - CDP = 'CDP', -} - -type DataCollectorRequest = { id: string }; - -export type DataCollector = { - start: (input: DataCollectorRequest) => Promise; - stop: (input: DataCollectorRequest) => Promise; - getName: () => DataCollectorName; - close: () => Promise; -}; diff --git a/packages/grafana-e2e/cypress/plugins/benchmark/formatting.ts b/packages/grafana-e2e/cypress/plugins/benchmark/formatting.ts deleted file mode 100644 index 3ecb983a027..00000000000 --- a/packages/grafana-e2e/cypress/plugins/benchmark/formatting.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { fromPairs } from 'lodash'; - -import { CollectedData, DataCollectorName } from './DataCollector'; - -type Stats = { - sum: number; - min: number; - max: number; - count: number; - avg: number; - time: number; -}; - -export enum MeasurementName { - DataRenderDelay = 'DataRenderDelay', -} - -type LivePerformanceAppStats = Record; - -const isLivePerformanceAppStats = (data: CollectedData[]): data is LivePerformanceAppStats[] => - data.some((st) => { - const stat = st?.[MeasurementName.DataRenderDelay]; - return Array.isArray(stat) && Boolean(stat?.length); - }); - -type FormattedStats = { - total: { - count: number[]; - avg: number[]; - }; - lastInterval: { - avg: number[]; - min: number[]; - max: number[]; - count: number[]; - }; -}; - -export const formatAppStats = (allStats: CollectedData[]) => { - if (!isLivePerformanceAppStats(allStats)) { - return {}; - } - - const names = Object.keys(MeasurementName) as MeasurementName[]; - - return fromPairs( - names.map((name) => { - const statsForMeasurement = allStats.map((s) => s[name]); - const res: FormattedStats = { - total: { - count: [], - avg: [], - }, - lastInterval: { - avg: [], - min: [], - max: [], - count: [], - }, - }; - - statsForMeasurement.forEach((s) => { - const total = s.reduce( - (prev, next) => { - prev.count += next.count; - prev.avg += next.avg; - return prev; - }, - { count: 0, avg: 0 } - ); - res.total.count.push(Math.round(total.count)); - res.total.avg.push(Math.round(total.avg / s.length)); - - const lastInterval = s[s.length - 1]; - - res.lastInterval.avg.push(Math.round(lastInterval?.avg)); - res.lastInterval.min.push(Math.round(lastInterval?.min)); - res.lastInterval.max.push(Math.round(lastInterval?.max)); - res.lastInterval.count.push(Math.round(lastInterval?.count)); - }); - - return [name, res]; - }) - ); -}; - -type CDPData = { - eventCounts: Record; - fps: number; - tracingDataLoss: number; - warnings: Record; -}; - -const isCDPData = (data: any[]): data is CDPData[] => data.every((d) => typeof d.eventCounts === 'object'); - -type FormattedCDPData = { - minorGC: number[]; - majorGC: number[]; - droppedFrames: number[]; - fps: number[]; - tracingDataLossOccurred: boolean; - longTaskWarnings: number[]; -}; - -const emptyFormattedCDPData = (): FormattedCDPData => ({ - minorGC: [], - majorGC: [], - droppedFrames: [], - fps: [], - tracingDataLossOccurred: false, - longTaskWarnings: [], -}); - -const formatCDPData = (data: any): FormattedCDPData => { - if (!isCDPData(data)) { - return emptyFormattedCDPData(); - } - - return data.reduce((acc, next) => { - acc.majorGC.push((next.eventCounts.MajorGC as number) ?? 0); - acc.minorGC.push((next.eventCounts.MinorGC as number) ?? 0); - acc.fps.push(Math.round(next.fps) ?? 0); - acc.tracingDataLossOccurred = acc.tracingDataLossOccurred || Boolean(next.tracingDataLoss); - acc.droppedFrames.push((next.eventCounts.DroppedFrame as number) ?? 0); - acc.longTaskWarnings.push((next.warnings.LongTask as number) ?? 0); - return acc; - }, emptyFormattedCDPData()); -}; - -export const formatResults = ( - results: Array<{ appStats: CollectedData; collectorsData: CollectedData }> -): CollectedData => { - return { - ...formatAppStats(results.map(({ appStats }) => appStats)), - ...formatCDPData(results.map(({ collectorsData }) => collectorsData[DataCollectorName.CDP])), - - __raw: results, - }; -}; diff --git a/packages/grafana-e2e/cypress/plugins/benchmark/index.ts b/packages/grafana-e2e/cypress/plugins/benchmark/index.ts deleted file mode 100644 index 22307555d58..00000000000 --- a/packages/grafana-e2e/cypress/plugins/benchmark/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -import fs from 'fs'; -import { fromPairs } from 'lodash'; - -import { CDPDataCollector } from './CDPDataCollector'; -import { CollectedData, DataCollector } from './DataCollector'; -import { formatResults } from './formatting'; -const remoteDebuggingPortOptionPrefix = '--remote-debugging-port='; - -const getOrAddRemoteDebuggingPort = (args: string[]) => { - const existing = args.find((arg) => arg.startsWith(remoteDebuggingPortOptionPrefix)); - - if (existing) { - return Number(existing.substring(remoteDebuggingPortOptionPrefix.length)); - } - - const port = 40000 + Math.round(Math.random() * 25000); - args.push(`${remoteDebuggingPortOptionPrefix}${port}`); - return port; -}; - -let collectors: DataCollector[] = []; -let results: Array<{ appStats: CollectedData; collectorsData: CollectedData }> = []; - -const startBenchmarking = async ({ testName }: { testName: string }) => { - await Promise.all(collectors.map((coll) => coll.start({ id: testName }))); - - return true; -}; - -const stopBenchmarking = async ({ testName, appStats }: { testName: string; appStats: CollectedData }) => { - const data = await Promise.all(collectors.map(async (coll) => [coll.getName(), await coll.stop({ id: testName })])); - - results.push({ - collectorsData: fromPairs(data), - appStats: appStats, - }); - - return true; -}; -const afterRun = async () => { - await Promise.all(collectors.map((coll) => coll.close())); - collectors = []; - results = []; -}; - -const afterSpec = (resultsFolder: string) => async (spec: { name: string }) => { - fs.writeFileSync(`${resultsFolder}/${spec.name}-${Date.now()}.json`, JSON.stringify(formatResults(results), null, 2)); - - results = []; -}; - -export const initialize: Cypress.PluginConfig = (on, config) => { - const resultsFolder = config.env['BENCHMARK_PLUGIN_RESULTS_FOLDER']; - - if (!fs.existsSync(resultsFolder)) { - fs.mkdirSync(resultsFolder, { recursive: true }); - console.log(`Created folder for benchmark results ${resultsFolder}`); - } - - on('before:browser:launch', async (browser, options) => { - if (browser.family !== 'chromium' || browser.name === 'electron') { - throw new Error('benchmarking plugin requires chrome'); - } - - const { args } = options; - - const port = getOrAddRemoteDebuggingPort(args); - collectors.push(new CDPDataCollector({ port })); - - args.push('--start-fullscreen'); - - console.log( - `initialized benchmarking plugin with ${collectors.length} collectors: ${collectors - .map((col) => col.getName()) - .join(', ')}` - ); - - return options; - }); - - on('task', { - startBenchmarking, - stopBenchmarking, - }); - - on('after:run', afterRun); - on('after:spec', afterSpec(resultsFolder)); -}; diff --git a/packages/grafana-e2e/cypress/plugins/benchmark/tracelib.d.ts b/packages/grafana-e2e/cypress/plugins/benchmark/tracelib.d.ts deleted file mode 100644 index 2ba83b35594..00000000000 --- a/packages/grafana-e2e/cypress/plugins/benchmark/tracelib.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -type TraceEvent = { - name: string; -}; - -declare class Tracelib { - constructor(private events: TraceEvent[]) {} - - getFPS: () => { times: number[]; values: number[] }; - getWarningCounts: () => Record; -} -declare module 'tracelib' { - export = Tracelib; - - export { TraceEvent }; -} diff --git a/packages/grafana-e2e/cypress/plugins/compareScreenshots.js b/packages/grafana-e2e/cypress/plugins/compareScreenshots.js deleted file mode 100644 index 426d12adac5..00000000000 --- a/packages/grafana-e2e/cypress/plugins/compareScreenshots.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; -const BlinkDiff = require('blink-diff'); -const { resolve } = require('path'); - -// @todo use npmjs.com/pixelmatch or an available cypress plugin -const compareScreenshots = async ({ config, screenshotsFolder, specName }) => { - const name = config.name || config; // @todo use `??` - const threshold = config.threshold || 0.001; // @todo use `??` - - const imageAPath = `${screenshotsFolder}/${specName}/${name}.png`; - const imageBPath = resolve(`${screenshotsFolder}/../expected/${specName}/${name}.png`); - - const imageOutputPath = screenshotsFolder.endsWith('actual') ? imageAPath.replace('.png', '.diff.png') : undefined; - - const { code } = await new Promise((resolve, reject) => { - new BlinkDiff({ - imageAPath, - imageBPath, - imageOutputPath, - threshold, - thresholdType: BlinkDiff.THRESHOLD_PERCENT, - }).run((error, result) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); - - if (code <= 1) { - let msg = `\nThe screenshot [${imageAPath}] differs from [${imageBPath}]`; - msg += '\n'; - msg += '\nCheck the Artifacts tab in the CircleCi build output for the actual screenshots.'; - msg += '\n'; - msg += '\n If the difference between expected and outcome is NOT acceptable then do the following:'; - msg += '\n - Check the code for changes that causes this difference, fix that and retry.'; - msg += '\n'; - msg += '\n If the difference between expected and outcome is acceptable then do the following:'; - msg += '\n - Replace the expected image with the outcome and retry.'; - msg += '\n'; - throw new Error(msg); - } else { - // Must return a value - return true; - } -}; - -module.exports = compareScreenshots; diff --git a/packages/grafana-e2e/cypress/plugins/extendConfig.js b/packages/grafana-e2e/cypress/plugins/extendConfig.js deleted file mode 100644 index 85b9088cd98..00000000000 --- a/packages/grafana-e2e/cypress/plugins/extendConfig.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict'; -const { - promises: { readFile }, -} = require('fs'); -const { resolve } = require('path'); - -// @todo use https://github.com/bahmutov/cypress-extends when possible -module.exports = async (baseConfig) => { - // From CLI - const { - env: { CWD, UPDATE_SCREENSHOTS }, - } = baseConfig; - - if (CWD) { - // @todo: https://github.com/cypress-io/cypress/issues/6406 - const jsonReporter = require.resolve('@mochajs/json-file-reporter'); - - // @todo `baseUrl: env.CYPRESS_BASEURL` - const projectConfig = { - fixturesFolder: `${CWD}/cypress/fixtures`, - integrationFolder: `${CWD}/cypress/integration`, - reporter: jsonReporter, - reporterOptions: { - output: `${CWD}/cypress/report.json`, - }, - screenshotsFolder: `${CWD}/cypress/screenshots/${UPDATE_SCREENSHOTS ? 'expected' : 'actual'}`, - videosFolder: `${CWD}/cypress/videos`, - }; - - const customProjectConfig = await readFile(`${CWD}/cypress.json`, 'utf8') - .then(JSON.parse) - .then((config) => { - const pathKeys = [ - 'fileServerFolder', - 'fixturesFolder', - 'ignoreTestFiles', - 'integrationFolder', - 'pluginsFile', - 'screenshotsFolder', - 'supportFile', - 'testFiles', - 'videosFolder', - ]; - - return Object.fromEntries( - Object.entries(config).map(([key, value]) => { - if (pathKeys.includes(key)) { - return [key, resolve(CWD, value)]; - } else { - return [key, value]; - } - }) - ); - }) - .catch((error) => { - if (error.code === 'ENOENT') { - // File is optional - return {}; - } else { - // Unexpected error - throw error; - } - }); - - return { - ...baseConfig, - ...projectConfig, - ...customProjectConfig, - reporterOptions: { - ...baseConfig.reporterOptions, - ...projectConfig.reporterOptions, - ...customProjectConfig.reporterOptions, - }, - }; - } else { - // Temporary legacy support for Grafana core (using `yarn start`) - return baseConfig; - } -}; diff --git a/packages/grafana-e2e/cypress/plugins/index.js b/packages/grafana-e2e/cypress/plugins/index.js deleted file mode 100644 index 7ec85fecae1..00000000000 --- a/packages/grafana-e2e/cypress/plugins/index.js +++ /dev/null @@ -1,73 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const benchmarkPlugin = require('./benchmark'); -const compareScreenshots = require('./compareScreenshots'); -const extendConfig = require('./extendConfig'); -const readProvisions = require('./readProvisions'); -const typescriptPreprocessor = require('./typescriptPreprocessor'); - -module.exports = (on, config) => { - if (config.env['BENCHMARK_PLUGIN_ENABLED'] === true) { - benchmarkPlugin.initialize(on, config); - } - - on('file:preprocessor', typescriptPreprocessor); - on('task', { compareScreenshots, readProvisions }); - on('task', { - log({ message, optional }) { - optional ? console.log(message, optional) : console.log(message); - return null; - }, - }); - on('task', { - getJSONFilesFromDir: async ({ projectPath, relativePath }) => { - const directoryPath = path.join(projectPath, relativePath); - const jsonFiles = fs.readdirSync(directoryPath); - return jsonFiles - .filter((fileName) => /.json$/i.test(fileName)) - .map((fileName) => { - const fileBuffer = fs.readFileSync(path.join(directoryPath, fileName)); - return JSON.parse(fileBuffer); - }); - }, - }); - - // Make recordings higher resolution - // https://www.cypress.io/blog/2021/03/01/generate-high-resolution-videos-and-screenshots/ - on('before:browser:launch', (browser = {}, launchOptions) => { - console.log('launching browser %s is headless? %s', browser.name, browser.isHeadless); - - // the browser width and height we want to get - // our screenshots and videos will be of that resolution - const width = 1920; - const height = 1080; - - console.log('setting the browser window size to %d x %d', width, height); - - if (browser.name === 'chrome' && browser.isHeadless) { - launchOptions.args.push(`--window-size=${width},${height}`); - - // force screen to be non-retina and just use our given resolution - launchOptions.args.push('--force-device-scale-factor=1'); - } - - if (browser.name === 'electron' && browser.isHeadless) { - // might not work on CI for some reason - launchOptions.preferences.width = width; - launchOptions.preferences.height = height; - } - - if (browser.name === 'firefox' && browser.isHeadless) { - launchOptions.args.push(`--width=${width}`); - launchOptions.args.push(`--height=${height}`); - } - - // IMPORTANT: return the updated browser launch options - return launchOptions; - }); - - // Always extend with this library's config and return for diffing - // @todo remove this when possible: https://github.com/cypress-io/cypress/issues/5674 - return extendConfig(config); -}; diff --git a/packages/grafana-e2e/cypress/plugins/readProvisions.js b/packages/grafana-e2e/cypress/plugins/readProvisions.js deleted file mode 100644 index f82542d2070..00000000000 --- a/packages/grafana-e2e/cypress/plugins/readProvisions.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; -const { - promises: { readFile }, -} = require('fs'); -const { resolve: resolvePath } = require('path'); -const { parse: parseYml } = require('yaml'); - -const readProvision = (filePath) => readFile(filePath, 'utf8').then((contents) => parseYml(contents)); - -const readProvisions = (filePaths) => Promise.all(filePaths.map(readProvision)); - -// Paths are relative to /provisioning -module.exports = ({ CWD, filePaths }) => - readProvisions(filePaths.map((filePath) => resolvePath(CWD, 'provisioning', filePath))); diff --git a/packages/grafana-e2e/cypress/plugins/typescriptPreprocessor.js b/packages/grafana-e2e/cypress/plugins/typescriptPreprocessor.js deleted file mode 100644 index be506578188..00000000000 --- a/packages/grafana-e2e/cypress/plugins/typescriptPreprocessor.js +++ /dev/null @@ -1,42 +0,0 @@ -const wp = require('@cypress/webpack-preprocessor'); -const { resolve } = require('path'); - -const anyNodeModules = /node_modules/; -const packageRoot = resolve(`${__dirname}/../../`); -const packageModules = `${packageRoot}/node_modules`; - -const webpackOptions = { - module: { - rules: [ - { - include: (modulePath) => { - if (!anyNodeModules.test(modulePath)) { - // Is a file within the project - return true; - } else { - // Is a file within this package - return modulePath.startsWith(packageRoot) && !modulePath.startsWith(packageModules); - } - }, - test: /\.ts$/, - use: [ - { - loader: 'ts-loader', - options: { - transpileOnly: true, - }, - }, - ], - }, - ], - }, - resolve: { - extensions: ['.ts', '.js'], - }, -}; - -const options = { - webpackOptions, -}; - -module.exports = wp(options); diff --git a/packages/grafana-e2e/cypress/support/commands.ts b/packages/grafana-e2e/cypress/support/commands.ts deleted file mode 100644 index ce386d11d8f..00000000000 --- a/packages/grafana-e2e/cypress/support/commands.ts +++ /dev/null @@ -1,41 +0,0 @@ -import 'cypress-file-upload'; - -interface CompareScreenshotsConfig { - name: string; - threshold?: number; -} - -Cypress.Commands.add('compareScreenshots', (config: CompareScreenshotsConfig | string) => { - cy.task('compareScreenshots', { - config, - screenshotsFolder: Cypress.config('screenshotsFolder'), - specName: Cypress.spec.name, - }); -}); - -Cypress.Commands.add('logToConsole', (message: string, optional?: any) => { - cy.task('log', { message: '(' + new Date().toISOString() + ') ' + message, optional }); -}); - -Cypress.Commands.add('readProvisions', (filePaths: string[]) => { - cy.task('readProvisions', { - CWD: Cypress.env('CWD'), - filePaths, - }); -}); - -Cypress.Commands.add('getJSONFilesFromDir', (dirPath: string) => { - return cy.task('getJSONFilesFromDir', { - // CWD is set for plugins in the cli but not for the main grafana repo: https://github.com/grafana/grafana/blob/main/packages/grafana-e2e/cli.js#L12 - projectPath: Cypress.env('CWD') || Cypress.config().parentTestsFolder, - relativePath: dirPath, - }); -}); - -Cypress.Commands.add('startBenchmarking', (testName: string) => { - return cy.task('startBenchmarking', { testName }); -}); - -Cypress.Commands.add('stopBenchmarking', (testName: string, appStats: Record) => { - return cy.task('stopBenchmarking', { testName, appStats }); -}); diff --git a/packages/grafana-e2e/cypress/support/index.d.ts b/packages/grafana-e2e/cypress/support/index.d.ts deleted file mode 100644 index 3f8d1975936..00000000000 --- a/packages/grafana-e2e/cypress/support/index.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -declare namespace Cypress { - interface Chainable { - compareScreenshots(config: CompareScreenshotsConfig | string): Chainable; - logToConsole(message: string, optional?: any): void; - readProvisions(filePaths: string[]): Chainable; - getJSONFilesFromDir(dirPath: string): Chainable; - startBenchmarking(testName: string): void; - stopBenchmarking(testName: string, appStats: Record): void; - } -} diff --git a/packages/grafana-e2e/cypress/support/index.ts b/packages/grafana-e2e/cypress/support/index.ts deleted file mode 100644 index f84817e99fb..00000000000 --- a/packages/grafana-e2e/cypress/support/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -// yarn build fails with: -// >> /Users/hugo/go/src/github.com/grafana/grafana/node_modules/stringmap/stringmap.js:99 -// >> throw new Error("StringMap expected string key"); -// require('cypress-failed-log'); -import './commands'; - -Cypress.Screenshot.defaults({ - screenshotOnRunFailure: false, -}); - -const COMMAND_DELAY = 1000; - -if (Cypress.env('SLOWMO')) { - const commandsToModify = ['clear', 'click', 'contains', 'reload', 'then', 'trigger', 'type', 'visit']; - - commandsToModify.forEach((command) => { - // @ts-ignore -- https://github.com/cypress-io/cypress/issues/7807 - Cypress.Commands.overwrite(command, (originalFn, ...args) => { - const origVal = originalFn(...args); - - return new Promise((resolve) => { - setTimeout(() => resolve(origVal), COMMAND_DELAY); - }); - }); - }); -} - -// @todo remove when possible: https://github.com/cypress-io/cypress/issues/95 -Cypress.on('window:before:load', (win) => { - // @ts-ignore - delete win.fetch; -}); - -// See https://github.com/quasarframework/quasar/issues/2233 for details -const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/; -Cypress.on('uncaught:exception', (err) => { - /* returning false here prevents Cypress from failing the test */ - if (resizeObserverLoopErrRe.test(err.message)) { - return false; - } - return true; -}); - -// uncomment below to prevent Cypress from failing tests when unhandled errors are thrown -// Cypress.on('uncaught:exception', (err, runnable) => { -// // returning false here prevents Cypress from -// // failing the test -// return false; -// }); diff --git a/packages/grafana-e2e/cypress/tsconfig.json b/packages/grafana-e2e/cypress/tsconfig.json deleted file mode 100644 index b3f735c508e..00000000000 --- a/packages/grafana-e2e/cypress/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "declaration": false, - "module": "commonjs", - "types": ["cypress", "cypress-file-upload", "node"] - }, - "extends": "@grafana/tsconfig", - "include": ["**/*.ts"] -} diff --git a/packages/grafana-e2e/package.json b/packages/grafana-e2e/package.json deleted file mode 100644 index ec76cbd7184..00000000000 --- a/packages/grafana-e2e/package.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "author": "Grafana Labs", - "license": "Apache-2.0", - "name": "@grafana/e2e", - "version": "11.1.0-pre", - "description": "Grafana End-to-End Test Library", - "keywords": [ - "cli", - "grafana", - "e2e", - "typescript" - ], - "repository": { - "type": "git", - "url": "http://github.com/grafana/grafana.git", - "directory": "packages/grafana-e2e" - }, - "main": "src/index.ts", - "types": "src/index.ts", - "bin": { - "grafana-e2e": "bin/grafana-e2e.js" - }, - "publishConfig": { - "main": "dist/index.js", - "types": "dist/index.d.ts", - "access": "public" - }, - "files": [ - "cypress", - "dist", - "cli.js", - "cypress.json", - "./README.md", - "./CHANGELOG.md", - "LICENSE_APACHE2" - ], - "scripts": { - "build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts", - "bundle": "rollup -c rollup.config.ts", - "clean": "rimraf ./dist ./compiled ./package.tgz", - "open": "cypress open", - "start": "cypress run --browser=chrome", - "start-benchmark": "CYPRESS_NO_COMMAND_LOG=1 yarn start", - "test": "pushd test && node ../dist/bin/grafana-e2e.js run", - "typecheck": "tsc --emitDeclarationOnly false --noEmit", - "prepack": "cp package.json package.json.bak && node ../../scripts/prepare-packagejson.js", - "postpack": "mv package.json.bak package.json" - }, - "devDependencies": { - "@rollup/plugin-node-resolve": "15.2.3", - "@types/chrome-remote-interface": "0.31.10", - "@types/lodash": "4.14.195", - "@types/node": "18.18.4", - "@types/uuid": "9.0.2", - "esbuild": "0.18.12", - "rollup": "2.79.1", - "rollup-plugin-dts": "^5.0.0", - "rollup-plugin-esbuild": "5.0.0", - "rollup-plugin-node-externals": "^5.0.0", - "webpack": "5.89.0" - }, - "dependencies": { - "@babel/core": "7.23.2", - "@babel/preset-env": "7.23.2", - "@cypress/webpack-preprocessor": "5.17.1", - "@grafana/e2e-selectors": "11.1.0-pre", - "@grafana/schema": "11.1.0-pre", - "@grafana/tsconfig": "^1.3.0-rc1", - "@mochajs/json-file-reporter": "^1.2.0", - "babel-loader": "9.1.3", - "blink-diff": "1.0.13", - "chrome-remote-interface": "0.33.0", - "commander": "8.3.0", - "cypress": "9.5.1", - "cypress-file-upload": "5.0.8", - "devtools-protocol": "0.0.1170333", - "execa": "5.1.1", - "lodash": "4.17.21", - "mocha": "10.2.0", - "resolve-bin": "1.0.1", - "rimraf": "5.0.1", - "tracelib": "1.0.1", - "ts-loader": "8.4.0", - "tslib": "2.6.0", - "typescript": "5.2.2", - "uuid": "9.0.0", - "yaml": "^2.0.0" - } -} diff --git a/packages/grafana-e2e/rollup.config.ts b/packages/grafana-e2e/rollup.config.ts deleted file mode 100644 index 95eeba012a7..00000000000 --- a/packages/grafana-e2e/rollup.config.ts +++ /dev/null @@ -1,29 +0,0 @@ -import resolve from '@rollup/plugin-node-resolve'; -import path from 'path'; -import dts from 'rollup-plugin-dts'; -import esbuild from 'rollup-plugin-esbuild'; -import { externals } from 'rollup-plugin-node-externals'; - -const pkg = require('./package.json'); - -export default [ - { - input: 'src/index.ts', - plugins: [externals({ deps: true, packagePath: './package.json' }), resolve(), esbuild({ target: 'node16' })], - output: [ - { - format: 'cjs', - sourcemap: true, - dir: path.dirname(pkg.publishConfig.main), - }, - ], - }, - { - input: './compiled/index.d.ts', - plugins: [dts()], - output: { - file: pkg.publishConfig.types, - format: 'es', - }, - }, -]; diff --git a/packages/grafana-e2e/src/flows/addDashboard.ts b/packages/grafana-e2e/src/flows/addDashboard.ts deleted file mode 100644 index 2526a03422e..00000000000 --- a/packages/grafana-e2e/src/flows/addDashboard.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; - -import { e2e } from '../index'; -import { getDashboardUid } from '../support/url'; - -import { DeleteDashboardConfig } from './deleteDashboard'; -import { selectOption } from './selectOption'; -import { setDashboardTimeRange, TimeRangeConfig } from './setDashboardTimeRange'; - -export interface AddAnnotationConfig { - dataSource: string; - dataSourceForm?: () => void; - name: string; -} - -export interface AddDashboardConfig { - annotations: AddAnnotationConfig[]; - timeRange: TimeRangeConfig; - title: string; - variables: PartialAddVariableConfig[]; -} - -interface AddVariableDefault { - hide: string; - type: string; -} - -interface AddVariableOptional { - constantValue?: string; - dataSource?: string; - label?: string; - query?: string; - regex?: string; - variableQueryForm?: (config: AddVariableConfig) => void; -} - -interface AddVariableRequired { - name: string; -} - -export type PartialAddVariableConfig = Partial & AddVariableOptional & AddVariableRequired; -export type AddVariableConfig = AddVariableDefault & AddVariableOptional & AddVariableRequired; - -/** - * This flow is used to add a dashboard with whatever configuration specified. - * @param config Configuration object. Currently supports configuring dashboard time range, annotations, and variables (support dependant on type). - * @see{@link AddDashboardConfig} - * - * @example - * ``` - * // Configuring a simple dashboard - * addDashboard({ - * timeRange: { - * from: '2022-10-03 00:00:00', - * to: '2022-10-03 23:59:59', - * zone: 'Coordinated Universal Time', - * }, - * title: 'Test Dashboard', - * }) - * ``` - * - * @example - * ``` - * // Configuring a dashboard with annotations - * addDashboard({ - * title: 'Test Dashboard', - * annotations: [ - * { - * // This should match the datasource name - * dataSource: 'azure-monitor', - * name: 'Test Annotation', - * dataSourceForm: () => { - * // Insert steps to create annotation using datasource form - * } - * } - * ] - * }) - * ``` - * - * @see{@link AddAnnotationConfig} - * - * @example - * ``` - * // Configuring a dashboard with variables - * addDashboard({ - * title: 'Test Dashboard', - * variables: [ - * { - * name: 'test-query-variable', - * label: 'Testing Query', - * hide: '', - * type: e2e.flows.VARIABLE_TYPE_QUERY, - * dataSource: 'azure-monitor', - * variableQueryForm: () => { - * // Insert steps to create variable using datasource form - * }, - * }, - * { - * name: 'test-constant-variable', - * label: 'Testing Constant', - * type: e2e.flows.VARIABLE_TYPE_CONSTANT, - * constantValue: 'constant', - * } - * ] - * }) - * ``` - * - * @see{@link AddVariableConfig} - * - * @see{@link https://github.com/grafana/grafana/blob/main/e2e/cloud-plugins-suite/azure-monitor.spec.ts Azure Monitor Tests for full examples} - */ -export const addDashboard = (config?: Partial) => { - const fullConfig: AddDashboardConfig = { - annotations: [], - title: `e2e-${uuidv4()}`, - variables: [], - ...config, - timeRange: { - from: '2020-01-01 00:00:00', - to: '2020-01-01 06:00:00', - zone: 'Coordinated Universal Time', - ...config?.timeRange, - }, - }; - - const { annotations, timeRange, title, variables } = fullConfig; - - e2e().logToConsole('Adding dashboard with title:', title); - - e2e.pages.AddDashboard.visit(); - - if (annotations.length > 0 || variables.length > 0) { - e2e.components.PageToolbar.item('Dashboard settings').click(); - addAnnotations(annotations); - - fullConfig.variables = addVariables(variables); - - e2e.components.BackButton.backArrow().should('be.visible').click({ force: true }); - } - - setDashboardTimeRange(timeRange); - - e2e.components.PageToolbar.item('Save dashboard').click(); - e2e.pages.SaveDashboardAsModal.newName().clear().type(title, { force: true }); - e2e.pages.SaveDashboardAsModal.save().click(); - e2e.flows.assertSuccessNotification(); - e2e.pages.AddDashboard.itemButton('Create new panel button').should('be.visible'); - - e2e().logToConsole('Added dashboard with title:', title); - - return e2e() - .url() - .should('contain', '/d/') - .then((url: string) => { - const uid = getDashboardUid(url); - - e2e.getScenarioContext().then(({ addedDashboards }: any) => { - e2e.setScenarioContext({ - addedDashboards: [...addedDashboards, { title, uid } as DeleteDashboardConfig], - }); - }); - - // @todo remove `wrap` when possible - return e2e().wrap( - { - config: fullConfig, - uid, - }, - { log: false } - ); - }); -}; - -const addAnnotation = (config: AddAnnotationConfig, isFirst: boolean) => { - if (isFirst) { - if (e2e.pages.Dashboard.Settings.Annotations.List.addAnnotationCTAV2) { - e2e.pages.Dashboard.Settings.Annotations.List.addAnnotationCTAV2().click(); - } else { - e2e.pages.Dashboard.Settings.Annotations.List.addAnnotationCTA().click(); - } - } else { - cy.contains('New query').click(); - } - - const { dataSource, dataSourceForm, name } = config; - - selectOption({ - container: e2e.components.DataSourcePicker.container(), - optionText: dataSource, - }); - - e2e.pages.Dashboard.Settings.Annotations.Settings.name().clear().type(name); - - if (dataSourceForm) { - dataSourceForm(); - } -}; - -const addAnnotations = (configs: AddAnnotationConfig[]) => { - if (configs.length > 0) { - e2e.pages.Dashboard.Settings.General.sectionItems('Annotations').click(); - } - - return configs.forEach((config, i) => addAnnotation(config, i === 0)); -}; - -export const VARIABLE_HIDE_LABEL = 'Label'; -export const VARIABLE_HIDE_NOTHING = ''; -export const VARIABLE_HIDE_VARIABLE = 'Variable'; - -export const VARIABLE_TYPE_AD_HOC_FILTERS = 'Ad hoc filters'; -export const VARIABLE_TYPE_CONSTANT = 'Constant'; -export const VARIABLE_TYPE_DATASOURCE = 'Datasource'; -export const VARIABLE_TYPE_QUERY = 'Query'; - -const addVariable = (config: PartialAddVariableConfig, isFirst: boolean): AddVariableConfig => { - const fullConfig = { - hide: VARIABLE_HIDE_NOTHING, - type: VARIABLE_TYPE_QUERY, - ...config, - }; - - if (isFirst) { - if (e2e.pages.Dashboard.Settings.Variables.List.addVariableCTAV2) { - e2e.pages.Dashboard.Settings.Variables.List.addVariableCTAV2().click(); - } else { - e2e.pages.Dashboard.Settings.Variables.List.addVariableCTA().click(); - } - } else { - e2e.pages.Dashboard.Settings.Variables.List.newButton().click(); - } - - const { constantValue, dataSource, label, name, query, regex, type, variableQueryForm } = fullConfig; - - // This field is key to many reactive changes - if (type !== VARIABLE_TYPE_QUERY) { - e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2() - .should('be.visible') - .within(() => { - e2e.components.Select.singleValue().should('have.text', 'Query').parent().click(); - }); - e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().find('input').type(`${type}{enter}`); - } - - if (label) { - e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type(label); - } - - e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type(name); - - if ( - dataSource && - (type === VARIABLE_TYPE_AD_HOC_FILTERS || type === VARIABLE_TYPE_DATASOURCE || type === VARIABLE_TYPE_QUERY) - ) { - e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsDataSourceSelect() - .should('be.visible') - .within(() => { - e2e.components.DataSourcePicker.inputV2().type(`${dataSource}{enter}`); - }); - } - - if (constantValue && type === VARIABLE_TYPE_CONSTANT) { - e2e.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInputV2().type(constantValue); - } - - if (type === VARIABLE_TYPE_QUERY) { - if (query) { - e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsQueryInput().type(query); - } - - if (regex) { - e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2().type(regex); - } - - if (variableQueryForm) { - variableQueryForm(fullConfig); - } - } - - // Avoid flakiness - e2e().focused().blur(); - - e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption() - .should('exist') - .within((previewOfValues) => { - if (type === VARIABLE_TYPE_CONSTANT) { - expect(previewOfValues.text()).equals(constantValue); - } - }); - - e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click(); - e2e.pages.Dashboard.Settings.Variables.Edit.General.applyButton().click(); - - return fullConfig; -}; - -const addVariables = (configs: PartialAddVariableConfig[]): AddVariableConfig[] => { - if (configs.length > 0) { - e2e.components.Tab.title('Variables').click(); - } - - return configs.map((config, i) => addVariable(config, i === 0)); -}; diff --git a/packages/grafana-e2e/src/flows/addDataSource.ts b/packages/grafana-e2e/src/flows/addDataSource.ts deleted file mode 100644 index 38779572bd4..00000000000 --- a/packages/grafana-e2e/src/flows/addDataSource.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; - -import { e2e } from '../index'; - -import { DeleteDataSourceConfig } from './deleteDataSource'; - -export interface AddDataSourceConfig { - basicAuth: boolean; - basicAuthPassword: string; - basicAuthUser: string; - expectedAlertMessage: string | RegExp; - form: () => void; - name: string; - skipTlsVerify: boolean; - type: string; - timeout?: number; - awaitHealth?: boolean; -} - -// @todo this actually returns type `Cypress.Chainable` -export const addDataSource = (config?: Partial) => { - const fullConfig: AddDataSourceConfig = { - basicAuth: false, - basicAuthPassword: '', - basicAuthUser: '', - expectedAlertMessage: 'Data source is working', - form: () => {}, - name: `e2e-${uuidv4()}`, - skipTlsVerify: false, - type: 'TestData', - ...config, - }; - - const { - basicAuth, - basicAuthPassword, - basicAuthUser, - expectedAlertMessage, - form, - name, - skipTlsVerify, - type, - timeout, - awaitHealth, - } = fullConfig; - - if (awaitHealth) { - e2e() - .intercept(/health/) - .as('health'); - } - - e2e().logToConsole('Adding data source with name:', name); - e2e.pages.AddDataSource.visit(); - e2e.pages.AddDataSource.dataSourcePluginsV2(type) - .scrollIntoView() - .should('be.visible') // prevents flakiness - .click(); - - e2e.pages.DataSource.name().clear(); - e2e.pages.DataSource.name().type(name); - - if (basicAuth) { - e2e().contains('label', 'Basic auth').scrollIntoView().click(); - e2e() - .contains('.gf-form-group', 'Basic Auth Details') - .should('be.visible') - .scrollIntoView() - .within(() => { - if (basicAuthUser) { - e2e().get('[placeholder=user]').type(basicAuthUser); - } - if (basicAuthPassword) { - e2e().get('[placeholder=Password]').type(basicAuthPassword); - } - }); - } - - if (skipTlsVerify) { - e2e().contains('label', 'Skip TLS Verify').scrollIntoView().click(); - } - - form(); - - e2e.pages.DataSource.saveAndTest().click(); - - if (awaitHealth) { - e2e().wait('@health', { timeout: timeout ?? e2e.config().defaultCommandTimeout }); - } - - // use the timeout passed in if it exists, otherwise, continue to use the default - e2e.pages.DataSource.alert() - .should('exist') - .contains(expectedAlertMessage, { - timeout: timeout ?? e2e.config().defaultCommandTimeout, - }); - e2e().logToConsole('Added data source with name:', name); - - return e2e() - .url() - .then(() => { - e2e.getScenarioContext().then(({ addedDataSources }: any) => { - e2e.setScenarioContext({ - addedDataSources: [...addedDataSources, { name } as DeleteDataSourceConfig], - }); - }); - - // @todo remove `wrap` when possible - return e2e().wrap( - { - config: fullConfig, - }, - { log: false } - ); - }); -}; diff --git a/packages/grafana-e2e/src/flows/addPanel.ts b/packages/grafana-e2e/src/flows/addPanel.ts deleted file mode 100644 index 0f214e51835..00000000000 --- a/packages/grafana-e2e/src/flows/addPanel.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; - -import { getScenarioContext } from '../support/scenarioContext'; - -import { configurePanel, PartialAddPanelConfig } from './configurePanel'; - -export const addPanel = (config?: Partial) => - getScenarioContext().then(({ lastAddedDataSource }: any) => - configurePanel({ - dataSourceName: lastAddedDataSource, - panelTitle: `e2e-${uuidv4()}`, - ...config, - isEdit: false, - }) - ); diff --git a/packages/grafana-e2e/src/flows/assertSuccessNotification.ts b/packages/grafana-e2e/src/flows/assertSuccessNotification.ts deleted file mode 100644 index 25f9228683c..00000000000 --- a/packages/grafana-e2e/src/flows/assertSuccessNotification.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { e2e } from '../index'; - -export const assertSuccessNotification = () => { - if (e2e.components.Alert.alertV2) { - e2e.components.Alert.alertV2('success').should('exist'); - } else { - e2e.components.Alert.alert('success').should('exist'); - } -}; diff --git a/packages/grafana-e2e/src/flows/configurePanel.ts b/packages/grafana-e2e/src/flows/configurePanel.ts deleted file mode 100644 index 59bc72216db..00000000000 --- a/packages/grafana-e2e/src/flows/configurePanel.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { e2e } from '..'; -import { getScenarioContext } from '../support/scenarioContext'; - -import { setDashboardTimeRange } from './setDashboardTimeRange'; -import { TimeRangeConfig } from './setTimeRange'; - -interface AddPanelOverrides { - dataSourceName: string; - queriesForm: (config: AddPanelConfig) => void; - panelTitle: string; -} - -interface EditPanelOverrides { - queriesForm?: (config: EditPanelConfig) => void; - panelTitle: string; -} - -interface ConfigurePanelDefault { - chartData: { - method: string; - route: string | RegExp; - }; - dashboardUid: string; - matchScreenshot: boolean; - saveDashboard: boolean; - screenshotName: string; - visitDashboardAtStart: boolean; // @todo remove when possible -} - -interface ConfigurePanelOptional { - dataSourceName?: string; - queriesForm?: (config: ConfigurePanelConfig) => void; - panelTitle?: string; - timeRange?: TimeRangeConfig; - visualizationName?: string; - timeout?: number; -} - -interface ConfigurePanelRequired { - isEdit: boolean; -} - -export type PartialConfigurePanelConfig = Partial & - ConfigurePanelOptional & - ConfigurePanelRequired; - -export type ConfigurePanelConfig = ConfigurePanelDefault & ConfigurePanelOptional & ConfigurePanelRequired; - -export type PartialAddPanelConfig = PartialConfigurePanelConfig & AddPanelOverrides; -export type AddPanelConfig = ConfigurePanelConfig & AddPanelOverrides; - -export type PartialEditPanelConfig = PartialConfigurePanelConfig & EditPanelOverrides; -export type EditPanelConfig = ConfigurePanelConfig & EditPanelOverrides; - -// @todo this actually returns type `Cypress.Chainable` -export const configurePanel = (config: PartialAddPanelConfig | PartialEditPanelConfig | PartialConfigurePanelConfig) => - getScenarioContext().then(({ lastAddedDashboardUid }: any) => { - const fullConfig: AddPanelConfig | EditPanelConfig | ConfigurePanelConfig = { - chartData: { - method: 'POST', - route: '/api/ds/query', - }, - dashboardUid: lastAddedDashboardUid, - matchScreenshot: false, - saveDashboard: true, - screenshotName: 'panel-visualization', - visitDashboardAtStart: true, - ...config, - }; - - const { - chartData, - dashboardUid, - dataSourceName, - isEdit, - matchScreenshot, - panelTitle, - queriesForm, - screenshotName, - timeRange, - visitDashboardAtStart, - visualizationName, - timeout, - } = fullConfig; - - if (visitDashboardAtStart) { - e2e.flows.openDashboard({ uid: dashboardUid }); - } - - if (isEdit) { - e2e.components.Panels.Panel.title(panelTitle).click(); - e2e.components.Panels.Panel.headerItems('Edit').click(); - } else { - try { - e2e.components.PageToolbar.itemButton('Add button').should('be.visible'); - e2e.components.PageToolbar.itemButton('Add button').click(); - } catch (e) { - // Depending on the screen size, the "Add" button might be hidden - e2e.components.PageToolbar.item('Show more items').click(); - e2e.components.PageToolbar.item('Add button').last().click(); - } - e2e.pages.AddDashboard.itemButton('Add new visualization menu item').should('be.visible'); - e2e.pages.AddDashboard.itemButton('Add new visualization menu item').click(); - } - - if (timeRange) { - setDashboardTimeRange(timeRange); - } - - // @todo alias '/**/*.js*' as '@pluginModule' when possible: https://github.com/cypress-io/cypress/issues/1296 - - e2e().intercept(chartData.method, chartData.route).as('chartData'); - - if (dataSourceName) { - e2e.components.DataSourcePicker.container().click().type(`${dataSourceName}{downArrow}{enter}`); - } - - // @todo instead wait for '@pluginModule' if not already loaded - e2e().wait(2000); - - // `panelTitle` is needed to edit the panel, and unlikely to have its value changed at that point - const changeTitle = panelTitle && !isEdit; - - if (changeTitle || visualizationName) { - if (changeTitle && panelTitle) { - e2e.components.PanelEditor.OptionsPane.fieldLabel('Panel options Title').type(`{selectall}${panelTitle}`); - } - - if (visualizationName) { - e2e.components.PluginVisualization.item(visualizationName).scrollIntoView().click(); - - // @todo wait for '@pluginModule' if not a core visualization and not already loaded - e2e().wait(2000); - } - } else { - // Consistently closed - closeOptions(); - } - - if (queriesForm) { - queriesForm(fullConfig); - - // Wait for a possible complex visualization to render (or something related, as this isn't necessary on the dashboard page) - // Can't assert that its HTML changed because a new query could produce the same results - e2e().wait(1000); - } - - // @todo enable when plugins have this implemented - //e2e.components.QueryEditorRow.actionButton('Disable/enable query').click(); - //e2e().wait('@chartData'); - //e2e.components.Panels.Panel.containerByTitle(panelTitle).find('.panel-content').contains('No data'); - //e2e.components.QueryEditorRow.actionButton('Disable/enable query').click(); - //e2e().wait('@chartData'); - - // Avoid annotations flakiness - e2e.components.RefreshPicker.runButtonV2().first().click({ force: true }); - - // Wait for RxJS - e2e().wait(timeout ?? e2e.config().defaultCommandTimeout); - - if (matchScreenshot) { - let visualization; - - visualization = e2e.components.Panels.Panel.containerByTitle(panelTitle).find('.panel-content'); - - visualization.scrollIntoView().screenshot(screenshotName); - e2e().compareScreenshots(screenshotName); - } - - // @todo remove `wrap` when possible - return e2e().wrap({ config: fullConfig }, { log: false }); - }); - -// @todo this actually returns type `Cypress.Chainable` -const closeOptions = () => e2e.components.PanelEditor.toggleVizOptions().click(); - -export const VISUALIZATION_ALERT_LIST = 'Alert list'; -export const VISUALIZATION_BAR_GAUGE = 'Bar gauge'; -export const VISUALIZATION_CLOCK = 'Clock'; -export const VISUALIZATION_DASHBOARD_LIST = 'Dashboard list'; -export const VISUALIZATION_GAUGE = 'Gauge'; -export const VISUALIZATION_GRAPH = 'Graph'; -export const VISUALIZATION_HEAT_MAP = 'Heatmap'; -export const VISUALIZATION_LOGS = 'Logs'; -export const VISUALIZATION_NEWS = 'News'; -export const VISUALIZATION_PIE_CHART = 'Pie Chart'; -export const VISUALIZATION_PLUGIN_LIST = 'Plugin list'; -export const VISUALIZATION_POLYSTAT = 'Polystat'; -export const VISUALIZATION_STAT = 'Stat'; -export const VISUALIZATION_TABLE = 'Table'; -export const VISUALIZATION_TEXT = 'Text'; -export const VISUALIZATION_WORLD_MAP = 'Worldmap Panel'; diff --git a/packages/grafana-e2e/src/flows/deleteDashboard.ts b/packages/grafana-e2e/src/flows/deleteDashboard.ts deleted file mode 100644 index d69c3da7146..00000000000 --- a/packages/grafana-e2e/src/flows/deleteDashboard.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { e2e } from '../index'; -import { fromBaseUrl } from '../support/url'; - -export interface DeleteDashboardConfig { - quick?: boolean; - title: string; - uid: string; -} - -export const deleteDashboard = ({ quick = false, title, uid }: DeleteDashboardConfig) => { - e2e().logToConsole('Deleting dashboard with uid:', uid); - - if (quick) { - quickDelete(uid); - } else { - uiDelete(uid, title); - } - - e2e().logToConsole('Deleted dashboard with uid:', uid); - - e2e.getScenarioContext().then(({ addedDashboards }: any) => { - e2e.setScenarioContext({ - addedDashboards: addedDashboards.filter((dashboard: DeleteDashboardConfig) => { - return dashboard.title !== title && dashboard.uid !== uid; - }), - }); - }); -}; - -const quickDelete = (uid: string) => { - e2e().request('DELETE', fromBaseUrl(`/api/dashboards/uid/${uid}`)); -}; - -const uiDelete = (uid: string, title: string) => { - e2e.pages.Dashboard.visit(uid); - e2e.components.PageToolbar.item('Dashboard settings').click(); - e2e.pages.Dashboard.Settings.General.deleteDashBoard().click(); - e2e.pages.ConfirmModal.delete().click(); - e2e.flows.assertSuccessNotification(); - - e2e.pages.Dashboards.visit(); - - // @todo replace `e2e.pages.Dashboards.dashboards` with this when argument is empty - if (e2e.components.Search.dashboardItems) { - e2e.components.Search.dashboardItems().each((item) => e2e().wrap(item).should('not.contain', title)); - } else { - e2e() - .get('[aria-label^="Dashboard search item "]') - .each((item) => e2e().wrap(item).should('not.contain', title)); - } -}; diff --git a/packages/grafana-e2e/src/flows/deleteDataSource.ts b/packages/grafana-e2e/src/flows/deleteDataSource.ts deleted file mode 100644 index f68b38f3a79..00000000000 --- a/packages/grafana-e2e/src/flows/deleteDataSource.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { e2e } from '../index'; -import { fromBaseUrl } from '../support/url'; - -export interface DeleteDataSourceConfig { - id: string; - name: string; - quick?: boolean; -} - -export const deleteDataSource = ({ id, name, quick = false }: DeleteDataSourceConfig) => { - e2e().logToConsole('Deleting data source with name:', name); - - if (quick) { - quickDelete(name); - } else { - uiDelete(name); - } - - e2e().logToConsole('Deleted data source with name:', name); - - e2e.getScenarioContext().then(({ addedDataSources }: any) => { - e2e.setScenarioContext({ - addedDataSources: addedDataSources.filter((dataSource: DeleteDataSourceConfig) => { - return dataSource.id !== id && dataSource.name !== name; - }), - }); - }); -}; - -const quickDelete = (name: string) => { - e2e().request('DELETE', fromBaseUrl(`/api/datasources/name/${name}`)); -}; - -const uiDelete = (name: string) => { - e2e.pages.DataSources.visit(); - e2e.pages.DataSources.dataSources(name).click(); - e2e.pages.DataSource.delete().click(); - e2e.pages.ConfirmModal.delete().click(); - - e2e.pages.DataSources.visit(); - - // @todo replace `e2e.pages.DataSources.dataSources` with this when argument is empty - e2e() - .get('[aria-label^="Data source list item "]') - .each((item) => e2e().wrap(item).should('not.contain', name)); -}; diff --git a/packages/grafana-e2e/src/flows/editPanel.ts b/packages/grafana-e2e/src/flows/editPanel.ts deleted file mode 100644 index c213465902a..00000000000 --- a/packages/grafana-e2e/src/flows/editPanel.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { configurePanel, PartialEditPanelConfig } from './configurePanel'; - -export const editPanel = (config: Partial) => - configurePanel({ - ...config, - isEdit: true, - }); diff --git a/packages/grafana-e2e/src/flows/importDashboard.ts b/packages/grafana-e2e/src/flows/importDashboard.ts deleted file mode 100644 index 2d9edeee690..00000000000 --- a/packages/grafana-e2e/src/flows/importDashboard.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { e2e } from '../index'; -import { fromBaseUrl, getDashboardUid } from '../support/url'; - -import { DeleteDashboardConfig } from '.'; - -type Panel = { - title: string; - [key: string]: unknown; -}; - -export type Dashboard = { title: string; panels: Panel[]; uid: string; [key: string]: unknown }; - -/** - * Smoke test a particular dashboard by quickly importing a json file and validate that all the panels finish loading - * @param dashboardToImport a sample dashboard - * @param queryTimeout a number of ms to wait for the imported dashboard to finish loading - * @param skipPanelValidation skip panel validation - */ -export const importDashboard = (dashboardToImport: Dashboard, queryTimeout?: number, skipPanelValidation?: boolean) => { - e2e().visit(fromBaseUrl('/dashboard/import')); - - // Note: normally we'd use 'click' and then 'type' here, but the json object is so big that using 'val' is much faster - e2e.components.DashboardImportPage.textarea().should('be.visible'); - e2e.components.DashboardImportPage.textarea().click(); - e2e.components.DashboardImportPage.textarea().invoke('val', JSON.stringify(dashboardToImport)); - e2e.components.DashboardImportPage.submit().should('be.visible').click(); - e2e.components.ImportDashboardForm.name().should('be.visible').click().clear().type(dashboardToImport.title); - e2e.components.ImportDashboardForm.submit().should('be.visible').click(); - - // wait for dashboard to load - e2e().wait(queryTimeout || 6000); - - // save the newly imported dashboard to context so it'll get properly deleted later - e2e() - .url() - .should('contain', '/d/') - .then((url: string) => { - const uid = getDashboardUid(url); - - e2e.getScenarioContext().then(({ addedDashboards }: { addedDashboards: DeleteDashboardConfig[] }) => { - e2e.setScenarioContext({ - addedDashboards: [...addedDashboards, { title: dashboardToImport.title, uid }], - }); - }); - - expect(dashboardToImport.uid).to.equal(uid); - }); - - if (!skipPanelValidation) { - dashboardToImport.panels.forEach((panel) => { - // Look at the json data - e2e.components.Panels.Panel.menu(panel.title).click({ force: true }); // force click because menu is hidden and show on hover - e2e.components.Panels.Panel.menuItems('Inspect').should('be.visible').click(); - e2e.components.Tab.title('JSON').should('be.visible').click(); - e2e.components.PanelInspector.Json.content().should('be.visible').contains('Panel JSON').click({ force: true }); - e2e.components.Select.option().should('be.visible').contains('Panel data').click(); - - // ensures that panel has loaded without knowingly hitting an error - // note: this does not prove that data came back as we expected it, - // it could get `state: Done` for no data for example - // but it ensures we didn't hit a 401 or 500 or something like that - e2e.components.CodeEditor.container() - .should('be.visible') - .contains(/"state": "(Done|Streaming)"/); - - // need to close panel - e2e.components.Drawer.General.close().click(); - }); - } -}; diff --git a/packages/grafana-e2e/src/flows/importDashboards.ts b/packages/grafana-e2e/src/flows/importDashboards.ts deleted file mode 100644 index 90a126b947a..00000000000 --- a/packages/grafana-e2e/src/flows/importDashboards.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { e2e } from '../index'; - -import { importDashboard, Dashboard } from './importDashboard'; - -/** - * Smoke test several dashboard json files from a test directory - * and validate that all the panels in each import finish loading their queries - * @param dirPath the relative path to a directory which contains json files representing dashboards, - * for example if your dashboards live in `cypress/testDashboards` you can pass `/testDashboards` - * @param queryTimeout a number of ms to wait for the imported dashboard to finish loading - * @param skipPanelValidation skips panel validation - */ -export const importDashboards = async (dirPath: string, queryTimeout?: number, skipPanelValidation?: boolean) => { - e2e() - .getJSONFilesFromDir(dirPath) - .then((jsonFiles: Dashboard[]) => { - jsonFiles.forEach((file) => { - importDashboard(file, queryTimeout || 6000, skipPanelValidation); - }); - }); -}; diff --git a/packages/grafana-e2e/src/flows/index.ts b/packages/grafana-e2e/src/flows/index.ts deleted file mode 100644 index e7fbfb3ce82..00000000000 --- a/packages/grafana-e2e/src/flows/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -export * from './addDashboard'; -export * from './addDataSource'; -export * from './addPanel'; -export * from './assertSuccessNotification'; -export * from './deleteDashboard'; -export * from './deleteDataSource'; -export * from './editPanel'; -export * from './login'; -export * from './openDashboard'; -export * from './openPanelMenuItem'; -export * from './revertAllChanges'; -export * from './saveDashboard'; -export * from './selectOption'; -export * from './setTimeRange'; -export * from './importDashboard'; -export * from './importDashboards'; -export * from './userPreferences'; - -export { - VISUALIZATION_ALERT_LIST, - VISUALIZATION_BAR_GAUGE, - VISUALIZATION_CLOCK, - VISUALIZATION_DASHBOARD_LIST, - VISUALIZATION_GAUGE, - VISUALIZATION_GRAPH, - VISUALIZATION_HEAT_MAP, - VISUALIZATION_LOGS, - VISUALIZATION_NEWS, - VISUALIZATION_PIE_CHART, - VISUALIZATION_PLUGIN_LIST, - VISUALIZATION_POLYSTAT, - VISUALIZATION_STAT, - VISUALIZATION_TABLE, - VISUALIZATION_TEXT, - VISUALIZATION_WORLD_MAP, -} from './configurePanel'; diff --git a/packages/grafana-e2e/src/flows/login.ts b/packages/grafana-e2e/src/flows/login.ts deleted file mode 100644 index 744e4332754..00000000000 --- a/packages/grafana-e2e/src/flows/login.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { e2e } from '../index'; -import { fromBaseUrl } from '../support/url'; - -const DEFAULT_USERNAME = 'admin'; -const DEFAULT_PASSWORD = 'admin'; - -const loginApi = (username: string, password: string) => { - cy.request({ - method: 'POST', - url: fromBaseUrl('/login'), - body: { - user: username, - password, - }, - }); -}; - -const loginUi = (username: string, password: string) => { - e2e().logToConsole('Logging in with username:', username); - e2e.pages.Login.visit(); - e2e.pages.Login.username() - .should('be.visible') // prevents flakiness - .type(username); - e2e.pages.Login.password().type(password); - e2e.pages.Login.submit().click(); - - // Local tests will have insecure credentials - if (password === DEFAULT_PASSWORD) { - e2e.pages.Login.skip().should('be.visible').click(); - } - - e2e().get('.login-page').should('not.exist'); -}; - -export const login = (username = DEFAULT_USERNAME, password = DEFAULT_PASSWORD, loginViaApi = true) => { - if (loginViaApi) { - loginApi(username, password); - } else { - loginUi(username, password); - } - e2e().logToConsole('Logged in with username:', username); -}; diff --git a/packages/grafana-e2e/src/flows/openDashboard.ts b/packages/grafana-e2e/src/flows/openDashboard.ts deleted file mode 100644 index c535099e63a..00000000000 --- a/packages/grafana-e2e/src/flows/openDashboard.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { e2e } from '../index'; -import { getScenarioContext } from '../support/scenarioContext'; - -import { setDashboardTimeRange, TimeRangeConfig } from './setDashboardTimeRange'; - -interface OpenDashboardDefault { - uid: string; -} - -interface OpenDashboardOptional { - timeRange?: TimeRangeConfig; - queryParams?: object; -} - -export type PartialOpenDashboardConfig = Partial & OpenDashboardOptional; -export type OpenDashboardConfig = OpenDashboardDefault & OpenDashboardOptional; - -// @todo this actually returns type `Cypress.Chainable` -export const openDashboard = (config?: PartialOpenDashboardConfig) => - getScenarioContext().then(({ lastAddedDashboardUid }: any) => { - const fullConfig: OpenDashboardConfig = { - uid: lastAddedDashboardUid, - ...config, - }; - - const { timeRange, uid, queryParams } = fullConfig; - - e2e.pages.Dashboard.visit(uid, queryParams); - - if (timeRange) { - setDashboardTimeRange(timeRange); - } - - // @todo remove `wrap` when possible - return e2e().wrap({ config: fullConfig }, { log: false }); - }); diff --git a/packages/grafana-e2e/src/flows/openPanelMenuItem.ts b/packages/grafana-e2e/src/flows/openPanelMenuItem.ts deleted file mode 100644 index 1ccd5206826..00000000000 --- a/packages/grafana-e2e/src/flows/openPanelMenuItem.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { e2e } from '../index'; - -export enum PanelMenuItems { - Edit = 'Edit', - Inspect = 'Inspect', - More = 'More...', - Extensions = 'Extensions', -} - -export const openPanelMenuItem = (menu: PanelMenuItems, panelTitle = 'Panel Title') => { - // we changed the way we open the panel menu in react panels with the new panel header - detectPanelType(panelTitle, (isAngularPanel) => { - if (isAngularPanel) { - e2e.components.Panels.Panel.title(panelTitle).should('be.visible').click(); - e2e.components.Panels.Panel.headerItems(menu).should('be.visible').click(); - } else { - e2e.components.Panels.Panel.menu(panelTitle).click({ force: true }); // force click because menu is hidden and show on hover - e2e.components.Panels.Panel.menuItems(menu).should('be.visible').click(); - } - }); -}; - -export const openPanelMenuExtension = (extensionTitle: string, panelTitle = 'Panel Title') => { - const menuItem = PanelMenuItems.Extensions; - // we changed the way we open the panel menu in react panels with the new panel header - detectPanelType(panelTitle, (isAngularPanel) => { - if (isAngularPanel) { - e2e.components.Panels.Panel.title(panelTitle).should('be.visible').click(); - e2e.components.Panels.Panel.headerItems(menuItem) - .should('be.visible') - .parent() - .parent() - .invoke('addClass', 'open'); - e2e.components.Panels.Panel.headerItems(extensionTitle).should('be.visible').click(); - } else { - e2e.components.Panels.Panel.menu(panelTitle).click({ force: true }); // force click because menu is hidden and show on hover - e2e.components.Panels.Panel.menuItems(menuItem).trigger('mouseover', { force: true }); - e2e.components.Panels.Panel.menuItems(extensionTitle).click({ force: true }); - } - }); -}; - -function detectPanelType(panelTitle: string, detected: (isAngularPanel: boolean) => void) { - e2e.components.Panels.Panel.title(panelTitle).then((el) => { - const isAngularPanel = el.find('plugin-component.ng-scope').length > 0; - - if (isAngularPanel) { - Cypress.log({ - name: 'detectPanelType', - displayName: 'detector', - message: 'Angular panel detected, will use legacy selectors.', - }); - } - - detected(isAngularPanel); - }); -} diff --git a/packages/grafana-e2e/src/flows/revertAllChanges.ts b/packages/grafana-e2e/src/flows/revertAllChanges.ts deleted file mode 100644 index 94b2e966bd6..00000000000 --- a/packages/grafana-e2e/src/flows/revertAllChanges.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { e2e } from '../index'; - -export const revertAllChanges = () => { - e2e.getScenarioContext().then(({ addedDashboards, addedDataSources, hasChangedUserPreferences }) => { - addedDashboards.forEach((dashboard: any) => e2e.flows.deleteDashboard({ ...dashboard, quick: true })); - addedDataSources.forEach((dataSource: any) => e2e.flows.deleteDataSource({ ...dataSource, quick: true })); - - if (hasChangedUserPreferences) { - e2e.flows.setDefaultUserPreferences(); - } - }); -}; diff --git a/packages/grafana-e2e/src/flows/saveDashboard.ts b/packages/grafana-e2e/src/flows/saveDashboard.ts deleted file mode 100644 index 37d5238f431..00000000000 --- a/packages/grafana-e2e/src/flows/saveDashboard.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { e2e } from '../index'; - -export const saveDashboard = () => { - e2e.components.PageToolbar.item('Save dashboard').click(); - - e2e.pages.SaveDashboardModal.save().click(); - - e2e.flows.assertSuccessNotification(); -}; diff --git a/packages/grafana-e2e/src/flows/selectOption.ts b/packages/grafana-e2e/src/flows/selectOption.ts deleted file mode 100644 index d47918c9fb5..00000000000 --- a/packages/grafana-e2e/src/flows/selectOption.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { e2e } from '../index'; - -export interface SelectOptionConfig { - clickToOpen?: boolean; - container: any; - forceClickOption?: boolean; - optionText: string | RegExp; -} - -// @todo this actually returns type `Cypress.Chainable` -export const selectOption = (config: SelectOptionConfig): any => { - const fullConfig: SelectOptionConfig = { - clickToOpen: true, - forceClickOption: false, - ...config, - }; - - const { clickToOpen, container, forceClickOption, optionText } = fullConfig; - - container.within(() => { - if (clickToOpen) { - e2e() - .get('[class$="-input-suffix"]', { timeout: 1000 }) - .then((element) => { - expect(Cypress.dom.isAttached(element)).to.eq(true); - e2e().get('[class$="-input-suffix"]', { timeout: 1000 }).click({ force: true }); - }); - } - }); - - return e2e.components.Select.option() - .filter((_, { textContent }) => { - if (textContent === null) { - return false; - } else if (typeof optionText === 'string') { - return textContent.includes(optionText); - } else { - return optionText.test(textContent); - } - }) - .scrollIntoView() - .click({ force: forceClickOption }); -}; diff --git a/packages/grafana-e2e/src/flows/setDashboardTimeRange.ts b/packages/grafana-e2e/src/flows/setDashboardTimeRange.ts deleted file mode 100644 index 566934c24e7..00000000000 --- a/packages/grafana-e2e/src/flows/setDashboardTimeRange.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { setTimeRange, TimeRangeConfig } from './setTimeRange'; - -export type { TimeRangeConfig }; - -export const setDashboardTimeRange = (config: TimeRangeConfig) => setTimeRange(config); diff --git a/packages/grafana-e2e/src/flows/setTimeRange.ts b/packages/grafana-e2e/src/flows/setTimeRange.ts deleted file mode 100644 index fe3528fea19..00000000000 --- a/packages/grafana-e2e/src/flows/setTimeRange.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { e2e } from '../index'; - -import { selectOption } from './selectOption'; - -export interface TimeRangeConfig { - from: string; - to: string; - zone?: string; -} - -export const setTimeRange = ({ from, to, zone }: TimeRangeConfig) => { - e2e.components.TimePicker.openButton().click(); - - if (zone) { - e2e().contains('button', 'Change time settings').click(); - e2e().log('setting time zone to ' + zone); - - if (e2e.components.TimeZonePicker.containerV2) { - selectOption({ - clickToOpen: true, - container: e2e.components.TimeZonePicker.containerV2(), - optionText: zone, - }); - } else { - selectOption({ - clickToOpen: true, - container: e2e.components.TimeZonePicker.container(), - optionText: zone, - }); - } - } - - // For smaller screens - e2e.components.TimePicker.absoluteTimeRangeTitle().click(); - - e2e.components.TimePicker.fromField().clear().type(from); - e2e.components.TimePicker.toField().clear().type(to); - - e2e.components.TimePicker.applyTimeRange().click(); -}; diff --git a/packages/grafana-e2e/src/flows/userPreferences.ts b/packages/grafana-e2e/src/flows/userPreferences.ts deleted file mode 100644 index 621bff04ce6..00000000000 --- a/packages/grafana-e2e/src/flows/userPreferences.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Preferences as UserPreferencesDTO } from '@grafana/schema/src/raw/preferences/x/preferences_types.gen'; - -import { e2e } from '..'; -import { fromBaseUrl } from '../support/url'; - -const defaultUserPreferences = { - timezone: '', // "Default" option -} as const; // TODO: when we update typescript >4.9 change to `as const satisfies UserPreferencesDTO` - -// Only accept preferences we have defaults for as arguments. To allow a new preference to be set, add a default for it -type UserPreferences = Pick; - -export function setUserPreferences(prefs: UserPreferences) { - e2e.setScenarioContext({ hasChangedUserPreferences: prefs !== defaultUserPreferences }); - - return cy.request({ - method: 'PUT', - url: fromBaseUrl('/api/user/preferences'), - body: prefs, - }); -} - -export function setDefaultUserPreferences() { - return setUserPreferences(defaultUserPreferences); -} diff --git a/packages/grafana-e2e/src/index.ts b/packages/grafana-e2e/src/index.ts deleted file mode 100644 index a7df2bec40f..00000000000 --- a/packages/grafana-e2e/src/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * A library for writing end-to-end tests for Grafana and its ecosystem. - * - * @packageDocumentation - */ -import { E2ESelectors, Selectors, selectors } from '@grafana/e2e-selectors'; - -import * as flows from './flows'; -import { e2eFactory } from './support'; -import { benchmark } from './support/benchmark'; -import { e2eScenario, ScenarioArguments } from './support/scenario'; -import { getScenarioContext, setScenarioContext } from './support/scenarioContext'; -import * as typings from './typings'; - -const e2eObject = { - env: (args: string) => Cypress.env(args), - config: () => Cypress.config(), - blobToBase64String: (blob: Blob) => Cypress.Blob.blobToBase64String(blob), - imgSrcToBlob: (url: string) => Cypress.Blob.imgSrcToBlob(url), - scenario: (args: ScenarioArguments) => e2eScenario(args), - benchmark, - pages: e2eFactory({ selectors: selectors.pages }), - typings, - components: e2eFactory({ selectors: selectors.components }), - flows, - getScenarioContext, - setScenarioContext, - getSelectors: (selectors: E2ESelectors) => e2eFactory({ selectors }), -}; - -export const e2e: (() => Cypress.cy) & typeof e2eObject = Object.assign(() => cy, e2eObject); diff --git a/packages/grafana-e2e/src/support/benchmark.ts b/packages/grafana-e2e/src/support/benchmark.ts deleted file mode 100644 index 70dbfb45028..00000000000 --- a/packages/grafana-e2e/src/support/benchmark.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { e2e } from '../'; - -export interface BenchmarkArguments { - name: string; - dashboard: { - folder: string; - delayAfterOpening: number; - skipPanelValidation: boolean; - }; - repeat: number; - duration: number; - appStats?: { - startCollecting?: (window: Window) => void; - collect: (window: Window) => Record; - }; - skipScenario?: boolean; -} - -export const benchmark = ({ - name, - skipScenario = false, - repeat, - duration, - appStats, - dashboard, -}: BenchmarkArguments) => { - if (skipScenario) { - describe(name, () => { - it.skip(name, () => {}); - }); - } - - describe(name, () => { - before(() => { - e2e.flows.login(e2e.env('USERNAME'), e2e.env('PASSWORD')); - }); - - beforeEach(() => { - e2e.flows.importDashboards(dashboard.folder, 1000, dashboard.skipPanelValidation); - Cypress.Cookies.preserveOnce('grafana_session'); - }); - - afterEach(() => e2e.flows.revertAllChanges()); - after(() => { - e2e().clearCookies(); - }); - - Array(repeat) - .fill(0) - .map((_, i) => { - const testName = `${name}-${i}`; - return it(testName, () => { - e2e.flows.openDashboard(); - - e2e().wait(dashboard.delayAfterOpening); - - if (appStats) { - const startCollecting = appStats.startCollecting; - if (startCollecting) { - e2e() - .window() - .then((win) => startCollecting(win)); - } - - e2e().startBenchmarking(testName); - e2e().wait(duration); - - e2e() - .window() - .then((win) => { - e2e().stopBenchmarking(testName, appStats.collect(win)); - }); - } else { - e2e().startBenchmarking(testName); - e2e().wait(duration); - e2e().stopBenchmarking(testName, {}); - } - }); - }); - }); -}; diff --git a/packages/grafana-e2e/src/support/index.ts b/packages/grafana-e2e/src/support/index.ts deleted file mode 100644 index 1ebbc3f881e..00000000000 --- a/packages/grafana-e2e/src/support/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './localStorage'; -export * from './scenarioContext'; -export * from './selector'; -export * from './types'; diff --git a/packages/grafana-e2e/src/support/localStorage.ts b/packages/grafana-e2e/src/support/localStorage.ts deleted file mode 100644 index eec99559ef6..00000000000 --- a/packages/grafana-e2e/src/support/localStorage.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { e2e } from '../index'; - -// @todo this actually returns type `Cypress.Chainable` -const get = (key: string): any => - e2e() - .wrap({ getLocalStorage: () => localStorage.getItem(key) }, { log: false }) - .invoke('getLocalStorage'); - -// @todo this actually returns type `Cypress.Chainable` -export const getLocalStorage = (key: string): any => - get(key).then((value: any) => { - if (value === null) { - return value; - } else { - return JSON.parse(value); - } - }); - -// @todo this actually returns type `Cypress.Chainable` -export const requireLocalStorage = (key: string): any => - get(key) // `getLocalStorage()` would turn 'null' into `null` - .should('not.equal', null) - .then((value: any) => JSON.parse(value as string)); diff --git a/packages/grafana-e2e/src/support/scenario.ts b/packages/grafana-e2e/src/support/scenario.ts deleted file mode 100644 index 85155ef365f..00000000000 --- a/packages/grafana-e2e/src/support/scenario.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { e2e } from '../'; - -export interface ScenarioArguments { - describeName: string; - itName: string; - scenario: Function; - skipScenario?: boolean; - addScenarioDataSource?: boolean; - addScenarioDashBoard?: boolean; - loginViaApi?: boolean; -} - -export const e2eScenario = ({ - describeName, - itName, - scenario, - skipScenario = false, - addScenarioDataSource = false, - addScenarioDashBoard = false, - loginViaApi = true, -}: ScenarioArguments) => { - describe(describeName, () => { - if (skipScenario) { - it.skip(itName, () => scenario()); - } else { - before(() => { - e2e.flows.login(e2e.env('USERNAME'), e2e.env('PASSWORD'), loginViaApi); - e2e.flows.setDefaultUserPreferences(); - }); - - beforeEach(() => { - Cypress.Cookies.preserveOnce('grafana_session'); - - if (addScenarioDataSource) { - e2e.flows.addDataSource(); - } - if (addScenarioDashBoard) { - e2e.flows.addDashboard(); - } - }); - - afterEach(() => e2e.flows.revertAllChanges()); - after(() => e2e().clearCookies()); - - it(itName, () => scenario()); - - // @todo remove when possible: https://github.com/cypress-io/cypress/issues/2831 - it('temporary', () => {}); - } - }); -}; diff --git a/packages/grafana-e2e/src/support/scenarioContext.ts b/packages/grafana-e2e/src/support/scenarioContext.ts deleted file mode 100644 index 741588da523..00000000000 --- a/packages/grafana-e2e/src/support/scenarioContext.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { DeleteDashboardConfig } from '../flows/deleteDashboard'; -import { DeleteDataSourceConfig } from '../flows/deleteDataSource'; -import { e2e } from '../index'; - -export interface ScenarioContext { - addedDashboards: DeleteDashboardConfig[]; - addedDataSources: DeleteDataSourceConfig[]; - lastAddedDashboard: string; // @todo rename to `lastAddedDashboardTitle` - lastAddedDashboardUid: string; - lastAddedDataSource: string; // @todo rename to `lastAddedDataSourceName` - lastAddedDataSourceId: string; - hasChangedUserPreferences: boolean; - [key: string]: any; -} - -const scenarioContext: ScenarioContext = { - addedDashboards: [], - addedDataSources: [], - hasChangedUserPreferences: false, - get lastAddedDashboard() { - return lastProperty(this.addedDashboards, 'title'); - }, - get lastAddedDashboardUid() { - return lastProperty(this.addedDashboards, 'uid'); - }, - get lastAddedDataSource() { - return lastProperty(this.addedDataSources, 'name'); - }, - get lastAddedDataSourceId() { - return lastProperty(this.addedDataSources, 'id'); - }, -}; - -const lastProperty = ( - items: T[], - key: K -) => items[items.length - 1]?.[key] ?? ''; - -export const getScenarioContext = (): Cypress.Chainable => - e2e() - .wrap( - { - getScenarioContext: (): ScenarioContext => ({ ...scenarioContext }), - }, - { log: false } - ) - .invoke({ log: false }, 'getScenarioContext'); - -export const setScenarioContext = (newContext: Partial): Cypress.Chainable => - e2e() - .wrap( - { - setScenarioContext: () => { - Object.entries(newContext).forEach(([key, value]) => { - scenarioContext[key] = value; - }); - }, - }, - { log: false } - ) - .invoke({ log: false }, 'setScenarioContext'); diff --git a/packages/grafana-e2e/src/support/selector.ts b/packages/grafana-e2e/src/support/selector.ts deleted file mode 100644 index 8e49d54afa1..00000000000 --- a/packages/grafana-e2e/src/support/selector.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface SelectorApi { - fromAriaLabel: (selector: string) => string; - fromDataTestId: (selector: string) => string; - fromSelector: (selector: string) => string; -} - -export const Selector: SelectorApi = { - fromAriaLabel: (selector: string) => `[aria-label="${selector}"]`, - fromDataTestId: (selector: string) => `[data-testid="${selector}"]`, - fromSelector: (selector: string) => selector, -}; diff --git a/packages/grafana-e2e/src/support/types.ts b/packages/grafana-e2e/src/support/types.ts deleted file mode 100644 index 60a275882ad..00000000000 --- a/packages/grafana-e2e/src/support/types.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { CssSelector, FunctionSelector, Selectors, StringSelector, UrlSelector } from '@grafana/e2e-selectors'; - -import { e2e } from '../index'; - -import { Selector } from './selector'; -import { fromBaseUrl } from './url'; - -export type VisitFunction = (args?: string, queryParams?: object) => Cypress.Chainable; -export type E2EVisit = { visit: VisitFunction }; -export type E2EFunction = ((text?: string, options?: CypressOptions) => Cypress.Chainable>) & - E2EFunctionWithOnlyOptions; -export type E2EFunctionWithOnlyOptions = (options?: CypressOptions) => Cypress.Chainable>; - -export type TypeSelectors = S extends StringSelector - ? E2EFunctionWithOnlyOptions - : S extends FunctionSelector - ? E2EFunction - : S extends CssSelector - ? E2EFunction - : S extends UrlSelector - ? E2EVisit & Omit, 'url'> - : S extends Record - ? E2EFunctions - : S; - -export type E2EFunctions = { - [P in keyof S]: TypeSelectors; -}; - -export type E2EObjects = E2EFunctions; - -export type E2EFactoryArgs = { selectors: S }; - -export type CypressOptions = Partial; - -const processSelectors = (e2eObjects: E2EFunctions, selectors: S): E2EFunctions => { - const logOutput = (data: any) => e2e().logToConsole('Retrieving Selector:', data); - const keys = Object.keys(selectors); - for (let index = 0; index < keys.length; index++) { - const key = keys[index]; - const value = selectors[key]; - - if (key === 'url') { - // @ts-ignore - e2eObjects['visit'] = (args?: string, queryParams?: object) => { - let parsedUrl = ''; - if (typeof value === 'string') { - parsedUrl = fromBaseUrl(value); - } - - if (typeof value === 'function' && args) { - parsedUrl = fromBaseUrl(value(args)); - } - - e2e().logToConsole('Visiting', parsedUrl); - if (queryParams) { - return e2e().visit({ url: parsedUrl, qs: queryParams }); - } else { - return e2e().visit(parsedUrl); - } - }; - - continue; - } - - if (typeof value === 'string') { - // @ts-ignore - e2eObjects[key] = (options?: CypressOptions) => { - logOutput(value); - const selector = value.startsWith('data-testid') - ? Selector.fromDataTestId(value) - : Selector.fromAriaLabel(value); - - return e2e().get(selector, options); - }; - - continue; - } - - if (typeof value === 'function') { - // @ts-ignore - e2eObjects[key] = function (textOrOptions?: string | CypressOptions, options?: CypressOptions) { - // the input can only be () - if (arguments.length === 0) { - const selector = value(undefined as unknown as string); - - logOutput(selector); - return e2e().get(selector); - } - - // the input can be (text) or (options) - if (arguments.length === 1) { - if (typeof textOrOptions === 'string') { - const selectorText = value(textOrOptions); - const selector = selectorText.startsWith('data-testid') - ? Selector.fromDataTestId(selectorText) - : Selector.fromAriaLabel(selectorText); - - logOutput(selector); - return e2e().get(selector); - } - const selector = value(undefined as unknown as string); - - logOutput(selector); - return e2e().get(selector, textOrOptions); - } - - // the input can only be (text, options) - if (arguments.length === 2 && typeof textOrOptions === 'string') { - const text = textOrOptions; - const selectorText = value(text); - const selector = text.startsWith('data-testid') - ? Selector.fromDataTestId(selectorText) - : Selector.fromAriaLabel(selectorText); - - logOutput(selector); - return e2e().get(selector, options); - } - }; - - continue; - } - - if (typeof value === 'object') { - // @ts-ignore - e2eObjects[key] = processSelectors({}, value); - } - } - - return e2eObjects; -}; - -export const e2eFactory = ({ selectors }: E2EFactoryArgs): E2EObjects => { - const e2eObjects: E2EFunctions = {} as E2EFunctions; - processSelectors(e2eObjects, selectors); - - return { ...e2eObjects }; -}; diff --git a/packages/grafana-e2e/src/support/url.ts b/packages/grafana-e2e/src/support/url.ts deleted file mode 100644 index 7af06d000ac..00000000000 --- a/packages/grafana-e2e/src/support/url.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { e2e } from '../index'; - -const getBaseUrl = () => e2e.env('BASE_URL') || e2e.config().baseUrl || 'http://localhost:3000'; - -export const fromBaseUrl = (url = '') => new URL(url, getBaseUrl()).href; - -export const getDashboardUid = (url: string): string => { - const matches = new URL(url).pathname.match(/\/d\/([^/]+)/); - if (!matches) { - throw new Error(`Couldn't parse uid from ${url}`); - } else { - return matches[1]; - } -}; diff --git a/packages/grafana-e2e/src/typings/index.ts b/packages/grafana-e2e/src/typings/index.ts deleted file mode 100644 index d4a4805a21f..00000000000 --- a/packages/grafana-e2e/src/typings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { undo } from './undo'; diff --git a/packages/grafana-e2e/src/typings/undo.ts b/packages/grafana-e2e/src/typings/undo.ts deleted file mode 100644 index 9ee3c1cda40..00000000000 --- a/packages/grafana-e2e/src/typings/undo.ts +++ /dev/null @@ -1,19 +0,0 @@ -// https://nodejs.org/api/os.html#os_os_platform -enum Platform { - osx = 'darwin', - windows = 'win32', - linux = 'linux', - aix = 'aix', - freebsd = 'freebsd', - openbsd = 'openbsd', - sunos = 'sunos', -} - -export const undo = () => { - switch (Cypress.platform) { - case Platform.osx: - return '{cmd}z'; - default: - return '{ctrl}z'; - } -}; diff --git a/packages/grafana-e2e/test/cypress/integration/0.cli.ts b/packages/grafana-e2e/test/cypress/integration/0.cli.ts deleted file mode 100644 index 52d23a4f242..00000000000 --- a/packages/grafana-e2e/test/cypress/integration/0.cli.ts +++ /dev/null @@ -1,3 +0,0 @@ -describe('CLI', () => { - it('compiles this file and runs it', () => {}); -}); diff --git a/packages/grafana-e2e/test/cypress/integration/1.api.ts b/packages/grafana-e2e/test/cypress/integration/1.api.ts deleted file mode 100644 index 00e790fab8e..00000000000 --- a/packages/grafana-e2e/test/cypress/integration/1.api.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { e2e } from '../../../dist'; - -describe('API', () => { - it('can be imported', () => { - expect(e2e).to.be.a('function'); - }); -}); diff --git a/packages/grafana-e2e/test/cypress/tsconfig.json b/packages/grafana-e2e/test/cypress/tsconfig.json deleted file mode 100644 index 16a03900977..00000000000 --- a/packages/grafana-e2e/test/cypress/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "@grafana/tsconfig", - "include": ["**/*.ts"], - "compilerOptions": { - "baseUrl": "../node_modules", - "types": ["cypress", "cypress-file-upload"] - } -} diff --git a/packages/grafana-e2e/tsconfig.build.json b/packages/grafana-e2e/tsconfig.build.json deleted file mode 100644 index 9ec189c28ea..00000000000 --- a/packages/grafana-e2e/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "exclude": ["dist", "node_modules", "**/*.test.ts*"], - "extends": "./tsconfig.json" -} diff --git a/packages/grafana-e2e/tsconfig.json b/packages/grafana-e2e/tsconfig.json deleted file mode 100644 index de9e076e2eb..00000000000 --- a/packages/grafana-e2e/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "declarationDir": "./compiled", - "emitDeclarationOnly": true, - "isolatedModules": true, - "rootDirs": ["."], - "types": ["cypress"] - }, - "exclude": ["dist/**/*"], - "extends": "@grafana/tsconfig", - "include": ["src/**/*.ts", "cypress/support/index.d.ts"] -} diff --git a/packages/grafana-ui/.eslintrc b/packages/grafana-ui/.eslintrc index 995c2caa896..13011848702 100644 --- a/packages/grafana-ui/.eslintrc +++ b/packages/grafana-ui/.eslintrc @@ -4,24 +4,24 @@ "no-restricted-imports": [ "error", { - "patterns": ["@grafana/runtime", "@grafana/data/*", "@grafana/ui", "@grafana/e2e", "@grafana/e2e-selectors/*"], + "patterns": ["@grafana/runtime", "@grafana/data/*", "@grafana/ui", "@grafana/e2e-selectors/*"], "paths": [ { "name": "react-i18next", "importNames": ["Trans", "t"], - "message": "Please import from grafana-ui/src/utils/i18n instead" - } - ] - } - ] + "message": "Please import from grafana-ui/src/utils/i18n instead", + }, + ], + }, + ], }, "overrides": [ { "files": ["**/*.{test,story}.{ts,tsx}"], "rules": { "no-restricted-imports": "off", - "react/prop-types": "off" - } - } - ] + "react/prop-types": "off", + }, + }, + ], } diff --git a/pkg/build/npm/npm.go b/pkg/build/npm/npm.go index 5b1a895ec49..04bcc53b322 100644 --- a/pkg/build/npm/npm.go +++ b/pkg/build/npm/npm.go @@ -22,7 +22,6 @@ var packages = []string{ "@grafana/ui", "@grafana/data", "@grafana/runtime", - "@grafana/e2e", "@grafana/e2e-selectors", "@grafana/schema", "@grafana/flamegraph", diff --git a/yarn.lock b/yarn.lock index 530f8edbf64..e1e9fa786a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -92,36 +92,13 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.23.2, @babel/compat-data@npm:^7.23.5, @babel/compat-data@npm:^7.24.4": +"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.23.5, @babel/compat-data@npm:^7.24.4": version: 7.24.4 resolution: "@babel/compat-data@npm:7.24.4" checksum: 10/e51faec0ac8259f03cc5029d2b4a944b4fee44cb5188c11530769d5beb81f384d031dba951febc3e33dbb48ceb8045b1184f5c1ac4c5f86ab1f5e951e9aaf7af languageName: node linkType: hard -"@babel/core@npm:7.23.2": - version: 7.23.2 - resolution: "@babel/core@npm:7.23.2" - dependencies: - "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.22.13" - "@babel/generator": "npm:^7.23.0" - "@babel/helper-compilation-targets": "npm:^7.22.15" - "@babel/helper-module-transforms": "npm:^7.23.0" - "@babel/helpers": "npm:^7.23.2" - "@babel/parser": "npm:^7.23.0" - "@babel/template": "npm:^7.22.15" - "@babel/traverse": "npm:^7.23.2" - "@babel/types": "npm:^7.23.0" - convert-source-map: "npm:^2.0.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.3" - semver: "npm:^6.3.1" - checksum: 10/b69d7008695b2ac7a3a2db83c5c712fbb79f7031c4480f6351cde327930e38873003d1d021059b729a1d0cb48093f1d384c64269b78f6189f50051fe4f64dc2d - languageName: node - linkType: hard - "@babel/core@npm:7.24.4, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.16, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.9, @babel/core@npm:^7.7.5": version: 7.24.4 resolution: "@babel/core@npm:7.24.4" @@ -145,7 +122,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.22.9, @babel/generator@npm:^7.23.0, @babel/generator@npm:^7.24.1, @babel/generator@npm:^7.24.4, @babel/generator@npm:^7.7.2": +"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.22.9, @babel/generator@npm:^7.24.1, @babel/generator@npm:^7.24.4, @babel/generator@npm:^7.7.2": version: 7.24.4 resolution: "@babel/generator@npm:7.24.4" dependencies: @@ -175,7 +152,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.22.15, @babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.23.6": +"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.23.6": version: 7.23.6 resolution: "@babel/helper-compilation-targets@npm:7.23.6" dependencies: @@ -220,36 +197,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.4.4": - version: 0.4.4 - resolution: "@babel/helper-define-polyfill-provider@npm:0.4.4" - dependencies: - "@babel/helper-compilation-targets": "npm:^7.22.6" - "@babel/helper-plugin-utils": "npm:^7.22.5" - debug: "npm:^4.1.1" - lodash.debounce: "npm:^4.0.8" - resolve: "npm:^1.14.2" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/16c312e40ecf2ead81f3ab7275387079071012d2363022c04cf16d56fe0d781185f3a517b928f4556c716ae45e0567b817b636d5cd2fee8fb2ce2b18a04c5bcd - languageName: node - linkType: hard - -"@babel/helper-define-polyfill-provider@npm:^0.5.0": - version: 0.5.0 - resolution: "@babel/helper-define-polyfill-provider@npm:0.5.0" - dependencies: - "@babel/helper-compilation-targets": "npm:^7.22.6" - "@babel/helper-plugin-utils": "npm:^7.22.5" - debug: "npm:^4.1.1" - lodash.debounce: "npm:^4.0.8" - resolve: "npm:^1.14.2" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/f849e816ec4b182a3e8fa8e09ff016f88bb95259cd6b2190b815c48f83c3d3b68e973a8ec72acc5086bfe93705cbd46ec089c06476421d858597780e42235a03 - languageName: node - linkType: hard - "@babel/helper-define-polyfill-provider@npm:^0.6.1": version: 0.6.1 resolution: "@babel/helper-define-polyfill-provider@npm:0.6.1" @@ -309,7 +256,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.23.0, @babel/helper-module-transforms@npm:^7.23.3": +"@babel/helper-module-transforms@npm:^7.23.3": version: 7.23.3 resolution: "@babel/helper-module-transforms@npm:7.23.3" dependencies: @@ -425,7 +372,7 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.23.2, @babel/helpers@npm:^7.24.4": +"@babel/helpers@npm:^7.24.4": version: 7.24.4 resolution: "@babel/helpers@npm:7.24.4" dependencies: @@ -448,7 +395,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.24.0, @babel/parser@npm:^7.24.1, @babel/parser@npm:^7.24.4": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.7, @babel/parser@npm:^7.24.0, @babel/parser@npm:^7.24.1, @babel/parser@npm:^7.24.4": version: 7.24.4 resolution: "@babel/parser@npm:7.24.4" bin: @@ -469,7 +416,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.22.15, @babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.24.1": +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.24.1" dependencies: @@ -480,7 +427,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.22.15, @babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.24.1": +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.24.1" dependencies: @@ -640,7 +587,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.22.5, @babel/plugin-syntax-import-assertions@npm:^7.24.1": +"@babel/plugin-syntax-import-assertions@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-syntax-import-assertions@npm:7.24.1" dependencies: @@ -651,7 +598,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-attributes@npm:^7.22.5, @babel/plugin-syntax-import-attributes@npm:^7.24.1": +"@babel/plugin-syntax-import-attributes@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-syntax-import-attributes@npm:7.24.1" dependencies: @@ -806,7 +753,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.22.5, @babel/plugin-transform-arrow-functions@npm:^7.24.1": +"@babel/plugin-transform-arrow-functions@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-arrow-functions@npm:7.24.1" dependencies: @@ -817,7 +764,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.23.2, @babel/plugin-transform-async-generator-functions@npm:^7.24.3": +"@babel/plugin-transform-async-generator-functions@npm:^7.24.3": version: 7.24.3 resolution: "@babel/plugin-transform-async-generator-functions@npm:7.24.3" dependencies: @@ -831,7 +778,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-to-generator@npm:^7.22.5, @babel/plugin-transform-async-to-generator@npm:^7.24.1": +"@babel/plugin-transform-async-to-generator@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-async-to-generator@npm:7.24.1" dependencies: @@ -844,7 +791,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoped-functions@npm:^7.22.5, @babel/plugin-transform-block-scoped-functions@npm:^7.24.1": +"@babel/plugin-transform-block-scoped-functions@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.24.1" dependencies: @@ -855,7 +802,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.23.0, @babel/plugin-transform-block-scoping@npm:^7.24.4": +"@babel/plugin-transform-block-scoping@npm:^7.24.4": version: 7.24.4 resolution: "@babel/plugin-transform-block-scoping@npm:7.24.4" dependencies: @@ -866,7 +813,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.22.5, @babel/plugin-transform-class-properties@npm:^7.24.1": +"@babel/plugin-transform-class-properties@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-class-properties@npm:7.24.1" dependencies: @@ -878,7 +825,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.22.11, @babel/plugin-transform-class-static-block@npm:^7.24.4": +"@babel/plugin-transform-class-static-block@npm:^7.24.4": version: 7.24.4 resolution: "@babel/plugin-transform-class-static-block@npm:7.24.4" dependencies: @@ -891,7 +838,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.22.15, @babel/plugin-transform-classes@npm:^7.24.1": +"@babel/plugin-transform-classes@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-classes@npm:7.24.1" dependencies: @@ -909,7 +856,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.22.5, @babel/plugin-transform-computed-properties@npm:^7.24.1": +"@babel/plugin-transform-computed-properties@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-computed-properties@npm:7.24.1" dependencies: @@ -921,7 +868,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.23.0, @babel/plugin-transform-destructuring@npm:^7.24.1": +"@babel/plugin-transform-destructuring@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-destructuring@npm:7.24.1" dependencies: @@ -932,7 +879,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.22.5, @babel/plugin-transform-dotall-regex@npm:^7.24.1": +"@babel/plugin-transform-dotall-regex@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-dotall-regex@npm:7.24.1" dependencies: @@ -944,7 +891,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-duplicate-keys@npm:^7.22.5, @babel/plugin-transform-duplicate-keys@npm:^7.24.1": +"@babel/plugin-transform-duplicate-keys@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-duplicate-keys@npm:7.24.1" dependencies: @@ -955,7 +902,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-dynamic-import@npm:^7.22.11, @babel/plugin-transform-dynamic-import@npm:^7.24.1": +"@babel/plugin-transform-dynamic-import@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-dynamic-import@npm:7.24.1" dependencies: @@ -967,7 +914,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-exponentiation-operator@npm:^7.22.5, @babel/plugin-transform-exponentiation-operator@npm:^7.24.1": +"@babel/plugin-transform-exponentiation-operator@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.24.1" dependencies: @@ -979,7 +926,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-export-namespace-from@npm:^7.22.11, @babel/plugin-transform-export-namespace-from@npm:^7.24.1": +"@babel/plugin-transform-export-namespace-from@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-export-namespace-from@npm:7.24.1" dependencies: @@ -1003,7 +950,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.22.15, @babel/plugin-transform-for-of@npm:^7.24.1": +"@babel/plugin-transform-for-of@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-for-of@npm:7.24.1" dependencies: @@ -1015,7 +962,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.22.5, @babel/plugin-transform-function-name@npm:^7.24.1": +"@babel/plugin-transform-function-name@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-function-name@npm:7.24.1" dependencies: @@ -1028,7 +975,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-json-strings@npm:^7.22.11, @babel/plugin-transform-json-strings@npm:^7.24.1": +"@babel/plugin-transform-json-strings@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-json-strings@npm:7.24.1" dependencies: @@ -1040,7 +987,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-literals@npm:^7.22.5, @babel/plugin-transform-literals@npm:^7.24.1": +"@babel/plugin-transform-literals@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-literals@npm:7.24.1" dependencies: @@ -1051,7 +998,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.22.11, @babel/plugin-transform-logical-assignment-operators@npm:^7.24.1": +"@babel/plugin-transform-logical-assignment-operators@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.24.1" dependencies: @@ -1063,7 +1010,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-member-expression-literals@npm:^7.22.5, @babel/plugin-transform-member-expression-literals@npm:^7.24.1": +"@babel/plugin-transform-member-expression-literals@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-member-expression-literals@npm:7.24.1" dependencies: @@ -1074,7 +1021,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-amd@npm:^7.23.0, @babel/plugin-transform-modules-amd@npm:^7.24.1": +"@babel/plugin-transform-modules-amd@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-modules-amd@npm:7.24.1" dependencies: @@ -1099,7 +1046,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.23.0, @babel/plugin-transform-modules-systemjs@npm:^7.24.1": +"@babel/plugin-transform-modules-systemjs@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-modules-systemjs@npm:7.24.1" dependencies: @@ -1113,7 +1060,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-umd@npm:^7.22.5, @babel/plugin-transform-modules-umd@npm:^7.24.1": +"@babel/plugin-transform-modules-umd@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-modules-umd@npm:7.24.1" dependencies: @@ -1137,7 +1084,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-new-target@npm:^7.22.5, @babel/plugin-transform-new-target@npm:^7.24.1": +"@babel/plugin-transform-new-target@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-new-target@npm:7.24.1" dependencies: @@ -1148,7 +1095,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.11, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.1": +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.24.1" dependencies: @@ -1160,7 +1107,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.22.11, @babel/plugin-transform-numeric-separator@npm:^7.24.1": +"@babel/plugin-transform-numeric-separator@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-numeric-separator@npm:7.24.1" dependencies: @@ -1172,7 +1119,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.22.15, @babel/plugin-transform-object-rest-spread@npm:^7.24.1": +"@babel/plugin-transform-object-rest-spread@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-object-rest-spread@npm:7.24.1" dependencies: @@ -1186,7 +1133,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-super@npm:^7.22.5, @babel/plugin-transform-object-super@npm:^7.24.1": +"@babel/plugin-transform-object-super@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-object-super@npm:7.24.1" dependencies: @@ -1198,7 +1145,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-catch-binding@npm:^7.22.11, @babel/plugin-transform-optional-catch-binding@npm:^7.24.1": +"@babel/plugin-transform-optional-catch-binding@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.24.1" dependencies: @@ -1210,7 +1157,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.23.0, @babel/plugin-transform-optional-chaining@npm:^7.24.1": +"@babel/plugin-transform-optional-chaining@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.1" dependencies: @@ -1223,7 +1170,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.22.15, @babel/plugin-transform-parameters@npm:^7.24.1": +"@babel/plugin-transform-parameters@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-parameters@npm:7.24.1" dependencies: @@ -1234,7 +1181,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.22.5, @babel/plugin-transform-private-methods@npm:^7.24.1": +"@babel/plugin-transform-private-methods@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-private-methods@npm:7.24.1" dependencies: @@ -1246,7 +1193,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.22.11, @babel/plugin-transform-private-property-in-object@npm:^7.24.1": +"@babel/plugin-transform-private-property-in-object@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-private-property-in-object@npm:7.24.1" dependencies: @@ -1260,7 +1207,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-property-literals@npm:^7.22.5, @babel/plugin-transform-property-literals@npm:^7.24.1": +"@babel/plugin-transform-property-literals@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-property-literals@npm:7.24.1" dependencies: @@ -1320,7 +1267,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.22.10, @babel/plugin-transform-regenerator@npm:^7.24.1": +"@babel/plugin-transform-regenerator@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-regenerator@npm:7.24.1" dependencies: @@ -1332,7 +1279,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-reserved-words@npm:^7.22.5, @babel/plugin-transform-reserved-words@npm:^7.24.1": +"@babel/plugin-transform-reserved-words@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-reserved-words@npm:7.24.1" dependencies: @@ -1343,7 +1290,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.22.5, @babel/plugin-transform-shorthand-properties@npm:^7.24.1": +"@babel/plugin-transform-shorthand-properties@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-shorthand-properties@npm:7.24.1" dependencies: @@ -1354,7 +1301,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.22.5, @babel/plugin-transform-spread@npm:^7.24.1": +"@babel/plugin-transform-spread@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-spread@npm:7.24.1" dependencies: @@ -1366,7 +1313,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-sticky-regex@npm:^7.22.5, @babel/plugin-transform-sticky-regex@npm:^7.24.1": +"@babel/plugin-transform-sticky-regex@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-sticky-regex@npm:7.24.1" dependencies: @@ -1377,7 +1324,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:^7.22.5, @babel/plugin-transform-template-literals@npm:^7.24.1": +"@babel/plugin-transform-template-literals@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-template-literals@npm:7.24.1" dependencies: @@ -1388,7 +1335,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-typeof-symbol@npm:^7.22.5, @babel/plugin-transform-typeof-symbol@npm:^7.24.1": +"@babel/plugin-transform-typeof-symbol@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.1" dependencies: @@ -1413,7 +1360,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-escapes@npm:^7.22.10, @babel/plugin-transform-unicode-escapes@npm:^7.24.1": +"@babel/plugin-transform-unicode-escapes@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-unicode-escapes@npm:7.24.1" dependencies: @@ -1424,7 +1371,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-property-regex@npm:^7.22.5, @babel/plugin-transform-unicode-property-regex@npm:^7.24.1": +"@babel/plugin-transform-unicode-property-regex@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.24.1" dependencies: @@ -1436,7 +1383,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.22.5, @babel/plugin-transform-unicode-regex@npm:^7.24.1": +"@babel/plugin-transform-unicode-regex@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-unicode-regex@npm:7.24.1" dependencies: @@ -1448,7 +1395,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-sets-regex@npm:^7.22.5, @babel/plugin-transform-unicode-sets-regex@npm:^7.24.1": +"@babel/plugin-transform-unicode-sets-regex@npm:^7.24.1": version: 7.24.1 resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.24.1" dependencies: @@ -1470,96 +1417,6 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:7.23.2": - version: 7.23.2 - resolution: "@babel/preset-env@npm:7.23.2" - dependencies: - "@babel/compat-data": "npm:^7.23.2" - "@babel/helper-compilation-targets": "npm:^7.22.15" - "@babel/helper-plugin-utils": "npm:^7.22.5" - "@babel/helper-validator-option": "npm:^7.22.15" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.22.15" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.22.15" - "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/plugin-syntax-class-properties": "npm:^7.12.13" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" - "@babel/plugin-syntax-import-assertions": "npm:^7.22.5" - "@babel/plugin-syntax-import-attributes": "npm:^7.22.5" - "@babel/plugin-syntax-import-meta": "npm:^7.10.4" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" - "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" - "@babel/plugin-transform-arrow-functions": "npm:^7.22.5" - "@babel/plugin-transform-async-generator-functions": "npm:^7.23.2" - "@babel/plugin-transform-async-to-generator": "npm:^7.22.5" - "@babel/plugin-transform-block-scoped-functions": "npm:^7.22.5" - "@babel/plugin-transform-block-scoping": "npm:^7.23.0" - "@babel/plugin-transform-class-properties": "npm:^7.22.5" - "@babel/plugin-transform-class-static-block": "npm:^7.22.11" - "@babel/plugin-transform-classes": "npm:^7.22.15" - "@babel/plugin-transform-computed-properties": "npm:^7.22.5" - "@babel/plugin-transform-destructuring": "npm:^7.23.0" - "@babel/plugin-transform-dotall-regex": "npm:^7.22.5" - "@babel/plugin-transform-duplicate-keys": "npm:^7.22.5" - "@babel/plugin-transform-dynamic-import": "npm:^7.22.11" - "@babel/plugin-transform-exponentiation-operator": "npm:^7.22.5" - "@babel/plugin-transform-export-namespace-from": "npm:^7.22.11" - "@babel/plugin-transform-for-of": "npm:^7.22.15" - "@babel/plugin-transform-function-name": "npm:^7.22.5" - "@babel/plugin-transform-json-strings": "npm:^7.22.11" - "@babel/plugin-transform-literals": "npm:^7.22.5" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.22.11" - "@babel/plugin-transform-member-expression-literals": "npm:^7.22.5" - "@babel/plugin-transform-modules-amd": "npm:^7.23.0" - "@babel/plugin-transform-modules-commonjs": "npm:^7.23.0" - "@babel/plugin-transform-modules-systemjs": "npm:^7.23.0" - "@babel/plugin-transform-modules-umd": "npm:^7.22.5" - "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.22.5" - "@babel/plugin-transform-new-target": "npm:^7.22.5" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.22.11" - "@babel/plugin-transform-numeric-separator": "npm:^7.22.11" - "@babel/plugin-transform-object-rest-spread": "npm:^7.22.15" - "@babel/plugin-transform-object-super": "npm:^7.22.5" - "@babel/plugin-transform-optional-catch-binding": "npm:^7.22.11" - "@babel/plugin-transform-optional-chaining": "npm:^7.23.0" - "@babel/plugin-transform-parameters": "npm:^7.22.15" - "@babel/plugin-transform-private-methods": "npm:^7.22.5" - "@babel/plugin-transform-private-property-in-object": "npm:^7.22.11" - "@babel/plugin-transform-property-literals": "npm:^7.22.5" - "@babel/plugin-transform-regenerator": "npm:^7.22.10" - "@babel/plugin-transform-reserved-words": "npm:^7.22.5" - "@babel/plugin-transform-shorthand-properties": "npm:^7.22.5" - "@babel/plugin-transform-spread": "npm:^7.22.5" - "@babel/plugin-transform-sticky-regex": "npm:^7.22.5" - "@babel/plugin-transform-template-literals": "npm:^7.22.5" - "@babel/plugin-transform-typeof-symbol": "npm:^7.22.5" - "@babel/plugin-transform-unicode-escapes": "npm:^7.22.10" - "@babel/plugin-transform-unicode-property-regex": "npm:^7.22.5" - "@babel/plugin-transform-unicode-regex": "npm:^7.22.5" - "@babel/plugin-transform-unicode-sets-regex": "npm:^7.22.5" - "@babel/preset-modules": "npm:0.1.6-no-external-plugins" - "@babel/types": "npm:^7.23.0" - babel-plugin-polyfill-corejs2: "npm:^0.4.6" - babel-plugin-polyfill-corejs3: "npm:^0.8.5" - babel-plugin-polyfill-regenerator: "npm:^0.5.3" - core-js-compat: "npm:^3.31.0" - semver: "npm:^6.3.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/7bc8aeed59047f99af2f608f3143044517582b6bd7b041e3c7a12eface47e0313a57e78fad2e0d450cda2ce6c58451d67493f3d3677c5c1031cf59b7db1161c3 - languageName: node - linkType: hard - "@babel/preset-env@npm:7.24.4, @babel/preset-env@npm:^7.22.9": version: 7.24.4 resolution: "@babel/preset-env@npm:7.24.4" @@ -1760,7 +1617,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.24.1": +"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.24.1": version: 7.24.1 resolution: "@babel/traverse@npm:7.24.1" dependencies: @@ -2004,32 +1861,6 @@ __metadata: languageName: node linkType: hard -"@cypress/request@npm:^2.88.10": - version: 2.88.11 - resolution: "@cypress/request@npm:2.88.11" - dependencies: - aws-sign2: "npm:~0.7.0" - aws4: "npm:^1.8.0" - caseless: "npm:~0.12.0" - combined-stream: "npm:~1.0.6" - extend: "npm:~3.0.2" - forever-agent: "npm:~0.6.1" - form-data: "npm:~2.3.2" - http-signature: "npm:~1.3.6" - is-typedarray: "npm:~1.0.0" - isstream: "npm:~0.1.2" - json-stringify-safe: "npm:~5.0.1" - mime-types: "npm:~2.1.19" - performance-now: "npm:^2.1.0" - qs: "npm:~6.10.3" - safe-buffer: "npm:^5.1.2" - tough-cookie: "npm:~2.5.0" - tunnel-agent: "npm:^0.6.0" - uuid: "npm:^8.3.2" - checksum: 10/a185eea04924ce7cad2cb167d4f265bf5d8356c687b38b29e606f7c902710f6ed3914bd246bae4da506668af0e70bbcdbd86fe2d8805812c79a9edc73a82abc4 - languageName: node - linkType: hard - "@cypress/request@npm:^3.0.0": version: 3.0.1 resolution: "@cypress/request@npm:3.0.1" @@ -2056,22 +1887,6 @@ __metadata: languageName: node linkType: hard -"@cypress/webpack-preprocessor@npm:5.17.1": - version: 5.17.1 - resolution: "@cypress/webpack-preprocessor@npm:5.17.1" - dependencies: - bluebird: "npm:3.7.1" - debug: "npm:^4.3.4" - lodash: "npm:^4.17.20" - peerDependencies: - "@babel/core": ^7.0.1 - "@babel/preset-env": ^7.0.0 - babel-loader: ^8.0.2 || ^9 - webpack: ^4 || ^5 - checksum: 10/34cb577fb2fb49817ae72a02ca759bb4c0066ed3d90d4e9d0d3a0a028975d4df4dd787d19f209aeaeb6d6896c66a6d4ce81a41c6eb39886a09a6f783417e94e3 - languageName: node - linkType: hard - "@cypress/webpack-preprocessor@npm:6.0.1": version: 6.0.1 resolution: "@cypress/webpack-preprocessor@npm:6.0.1" @@ -3704,51 +3519,6 @@ __metadata: languageName: unknown linkType: soft -"@grafana/e2e@workspace:packages/grafana-e2e": - version: 0.0.0-use.local - resolution: "@grafana/e2e@workspace:packages/grafana-e2e" - dependencies: - "@babel/core": "npm:7.23.2" - "@babel/preset-env": "npm:7.23.2" - "@cypress/webpack-preprocessor": "npm:5.17.1" - "@grafana/e2e-selectors": "npm:11.1.0-pre" - "@grafana/schema": "npm:11.1.0-pre" - "@grafana/tsconfig": "npm:^1.3.0-rc1" - "@mochajs/json-file-reporter": "npm:^1.2.0" - "@rollup/plugin-node-resolve": "npm:15.2.3" - "@types/chrome-remote-interface": "npm:0.31.10" - "@types/lodash": "npm:4.14.195" - "@types/node": "npm:18.18.4" - "@types/uuid": "npm:9.0.2" - babel-loader: "npm:9.1.3" - blink-diff: "npm:1.0.13" - chrome-remote-interface: "npm:0.33.0" - commander: "npm:8.3.0" - cypress: "npm:9.5.1" - cypress-file-upload: "npm:5.0.8" - devtools-protocol: "npm:0.0.1170333" - esbuild: "npm:0.18.12" - execa: "npm:5.1.1" - lodash: "npm:4.17.21" - mocha: "npm:10.2.0" - resolve-bin: "npm:1.0.1" - rimraf: "npm:5.0.1" - rollup: "npm:2.79.1" - rollup-plugin-dts: "npm:^5.0.0" - rollup-plugin-esbuild: "npm:5.0.0" - rollup-plugin-node-externals: "npm:^5.0.0" - tracelib: "npm:1.0.1" - ts-loader: "npm:8.4.0" - tslib: "npm:2.6.0" - typescript: "npm:5.2.2" - uuid: "npm:9.0.0" - webpack: "npm:5.89.0" - yaml: "npm:^2.0.0" - bin: - grafana-e2e: bin/grafana-e2e.js - languageName: unknown - linkType: soft - "@grafana/eslint-config@npm:7.0.0": version: 7.0.0 resolution: "@grafana/eslint-config@npm:7.0.0" @@ -5277,15 +5047,6 @@ __metadata: languageName: node linkType: hard -"@mochajs/json-file-reporter@npm:^1.2.0": - version: 1.3.0 - resolution: "@mochajs/json-file-reporter@npm:1.3.0" - peerDependencies: - mocha: 6.x || 7.x || 8.x - checksum: 10/b916b4ba048b2935026e61c09a84043f2f345c8f245a8505eb3e645fe300969fa9262fed6fc1391b25f1ea4e2120e5bfae35b33b49038d0306a44b86ef9675a3 - languageName: node - linkType: hard - "@monaco-editor/loader@npm:^1.4.0": version: 1.4.0 resolution: "@monaco-editor/loader@npm:1.4.0" @@ -9203,15 +8964,6 @@ __metadata: languageName: node linkType: hard -"@types/chrome-remote-interface@npm:0.31.10": - version: 0.31.10 - resolution: "@types/chrome-remote-interface@npm:0.31.10" - dependencies: - devtools-protocol: "npm:0.0.927104" - checksum: 10/8e5632813c86e295075dc2d590668a6bc5925cae310b56be8f64c4791e550e705330b49dac6dcce5667c7124d222910bbf0bc86b89c843e0261804d6993950cd - languageName: node - linkType: hard - "@types/common-tags@npm:^1.8.0": version: 1.8.4 resolution: "@types/common-tags@npm:1.8.4" @@ -9915,13 +9667,6 @@ __metadata: languageName: node linkType: hard -"@types/lodash@npm:4.14.195": - version: 4.14.195 - resolution: "@types/lodash@npm:4.14.195" - checksum: 10/d7c0902684508a3d0fdb60fe939a855f9f244fd4bf828eb75388a39d00b44e955dac16faea6c90c0ae4592a57784e45ceb6d51354af8dfd401de38046c685775 - languageName: node - linkType: hard - "@types/logfmt@npm:^1.2.3": version: 1.2.6 resolution: "@types/logfmt@npm:1.2.6" @@ -10031,20 +9776,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:18.18.4": - version: 18.18.4 - resolution: "@types/node@npm:18.18.4" - checksum: 10/2f55a7f0c603a3b56f2d24ec173d5f83be6470f638b551bab81821b6adf85d676656ed6ae267f994c029d48d4f1071b083ffdd5c7bbe7ae28533d1b4e1e83f12 - languageName: node - linkType: hard - -"@types/node@npm:^14.14.31": - version: 14.18.36 - resolution: "@types/node@npm:14.18.36" - checksum: 10/29e4c9fffea88e0b42345f76f93f4b13694dabfab6aab2fa9d05c8bea5731e878704c8bc2e25bc0f251f41e1b3fffe15263f4027a66a3de3504e12751d8ed43f - languageName: node - linkType: hard - "@types/node@npm:^16.0.0, @types/node@npm:^16.18.39": version: 16.18.50 resolution: "@types/node@npm:16.18.50" @@ -10484,13 +10215,6 @@ __metadata: languageName: node linkType: hard -"@types/uuid@npm:9.0.2": - version: 9.0.2 - resolution: "@types/uuid@npm:9.0.2" - checksum: 10/1754bcf3444e1e3aeadd6e774fc328eb53bc956665e2e8fb6ec127aa8e1f43d9a224c3d22a9a6233dca8dd81a12dc7fed4d84b8876dd5ec82d40f574f7ff8b68 - languageName: node - linkType: hard - "@types/uuid@npm:9.0.8": version: 9.0.8 resolution: "@types/uuid@npm:9.0.8" @@ -10957,7 +10681,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.11.5, @webassemblyjs/ast@npm:^1.12.1": +"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/ast@npm:1.12.1" dependencies: @@ -11043,7 +10767,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.11.5, @webassemblyjs/wasm-edit@npm:^1.12.1": +"@webassemblyjs/wasm-edit@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/wasm-edit@npm:1.12.1" dependencies: @@ -11084,7 +10808,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.11.5, @webassemblyjs/wasm-parser@npm:^1.12.1": +"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/wasm-parser@npm:1.12.1" dependencies: @@ -11464,7 +11188,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5": +"ajv@npm:^6.12.4, ajv@npm:^6.12.5": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -11516,7 +11240,7 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:4.1.1, ansi-colors@npm:^4.1.1": +"ansi-colors@npm:^4.1.1": version: 4.1.1 resolution: "ansi-colors@npm:4.1.1" checksum: 10/e862fddd0a9ca88f1e7c9312ea70674cec3af360c994762309f6323730525e92c77d2715ee5f08aa8f438b7ca18efe378af647f501fc92b15b8e4b3b52d09db4 @@ -11889,13 +11613,6 @@ __metadata: languageName: node linkType: hard -"asap@npm:~1.0.0": - version: 1.0.0 - resolution: "asap@npm:1.0.0" - checksum: 10/2844bc7ea09aa1840d7b415cb5f3b3a2a3ecd35c7b865a94a2ed4f09d2af763001d70951f950e495ee0a54b8284ffc8c01d2f39ecb0ae7d48ce74783d7cf9279 - languageName: node - linkType: hard - "asn1@npm:~0.2.3": version: 0.2.6 resolution: "asn1@npm:0.2.6" @@ -12121,7 +11838,7 @@ __metadata: languageName: node linkType: hard -"babel-loader@npm:9.1.3, babel-loader@npm:^9.0.0": +"babel-loader@npm:^9.0.0": version: 9.1.3 resolution: "babel-loader@npm:9.1.3" dependencies: @@ -12225,7 +11942,7 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.4.10, babel-plugin-polyfill-corejs2@npm:^0.4.6": +"babel-plugin-polyfill-corejs2@npm:^0.4.10": version: 0.4.10 resolution: "babel-plugin-polyfill-corejs2@npm:0.4.10" dependencies: @@ -12250,29 +11967,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.8.5": - version: 0.8.7 - resolution: "babel-plugin-polyfill-corejs3@npm:0.8.7" - dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.4.4" - core-js-compat: "npm:^3.33.1" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/defbc6de3d309c9639dd31223b5011707fcc0384037ac5959a1aefe16eb314562e1c1e5cfbce0af14a220d639ef92dfe5baf66664e9e6054656aca2841677622 - languageName: node - linkType: hard - -"babel-plugin-polyfill-regenerator@npm:^0.5.3": - version: 0.5.5 - resolution: "babel-plugin-polyfill-regenerator@npm:0.5.5" - dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.5.0" - peerDependencies: - "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/3a9b4828673b23cd648dcfb571eadcd9d3fadfca0361d0a7c6feeb5a30474e92faaa49f067a6e1c05e49b6a09812879992028ff3ef3446229ff132d6e1de7eb6 - languageName: node - linkType: hard - "babel-plugin-polyfill-regenerator@npm:^0.6.1": version: 0.6.1 resolution: "babel-plugin-polyfill-regenerator@npm:0.6.1" @@ -12465,19 +12159,6 @@ __metadata: languageName: node linkType: hard -"blink-diff@npm:1.0.13": - version: 1.0.13 - resolution: "blink-diff@npm:1.0.13" - dependencies: - pngjs-image: "npm:~0.11.5" - preceptor-core: "npm:~0.10.0" - promise: "npm:6.0.0" - bin: - blink-diff: bin/blink-diff - checksum: 10/fec6001ba4942217545b476031472e18bb2916139be1ed210d285ce04c79408f16223c5efccb37a54e9ef22f52d0c4e6e313ea846dfc00139e2a19124f18f281 - languageName: node - linkType: hard - "blob-polyfill@npm:7.0.20220408": version: 7.0.20220408 resolution: "blob-polyfill@npm:7.0.20220408" @@ -12634,13 +12315,6 @@ __metadata: languageName: node linkType: hard -"browser-stdout@npm:1.3.1": - version: 1.3.1 - resolution: "browser-stdout@npm:1.3.1" - checksum: 10/ac70a84e346bb7afc5045ec6f22f6a681b15a4057447d4cc1c48a25c6dedb302a49a46dd4ddfb5cdd9c96e0c905a8539be1b98ae7bc440512152967009ec7015 - languageName: node - linkType: hard - "browserify-zlib@npm:^0.1.4": version: 0.1.4 resolution: "browserify-zlib@npm:0.1.4" @@ -12650,7 +12324,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.14.5, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.2, browserslist@npm:^4.23.0": +"browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.2, browserslist@npm:^4.23.0": version: 4.23.0 resolution: "browserslist@npm:4.23.0" dependencies: @@ -12942,7 +12616,7 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.0.0, camelcase@npm:^6.2.0": +"camelcase@npm:^6.2.0": version: 6.3.0 resolution: "camelcase@npm:6.3.0" checksum: 10/8c96818a9076434998511251dcb2761a94817ea17dbdc37f47ac080bd088fc62c7369429a19e2178b993497132c8cbcf5cc1f44ba963e76782ba469c0474938d @@ -13155,25 +12829,6 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:3.5.3": - version: 3.5.3 - resolution: "chokidar@npm:3.5.3" - dependencies: - anymatch: "npm:~3.1.2" - braces: "npm:~3.0.2" - fsevents: "npm:~2.3.2" - glob-parent: "npm:~5.1.2" - is-binary-path: "npm:~2.1.0" - is-glob: "npm:~4.0.1" - normalize-path: "npm:~3.0.0" - readdirp: "npm:~3.6.0" - dependenciesMeta: - fsevents: - optional: true - checksum: 10/863e3ff78ee7a4a24513d2a416856e84c8e4f5e60efbe03e8ab791af1a183f569b62fc6f6b8044e2804966cb81277ddbbc1dc374fba3265bd609ea8efd62f5b3 - languageName: node - linkType: hard - "chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.3.1, chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" @@ -13568,13 +13223,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:8.3.0, commander@npm:^8.1.0, commander@npm:^8.3.0": - version: 8.3.0 - resolution: "commander@npm:8.3.0" - checksum: 10/6b7b5d334483ce24bd73c5dac2eab901a7dbb25fd983ea24a1eeac6e7166bb1967f641546e8abf1920afbde86a45fbfe5812fbc69d0dc451bb45ca416a12a3a3 - languageName: node - linkType: hard - "commander@npm:^10.0.0, commander@npm:^10.0.1": version: 10.0.1 resolution: "commander@npm:10.0.1" @@ -13589,13 +13237,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^5.1.0": - version: 5.1.0 - resolution: "commander@npm:5.1.0" - checksum: 10/3e2ef5c003c5179250161e42ce6d48e0e69a54af970c65b7f985c70095240c260fd647453efd4c2c5a31b30ce468f373dc70f769c2f54a2c014abc4792aaca28 - languageName: node - linkType: hard - "commander@npm:^6.2.0, commander@npm:^6.2.1": version: 6.2.1 resolution: "commander@npm:6.2.1" @@ -13603,6 +13244,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^8.1.0, commander@npm:^8.3.0": + version: 8.3.0 + resolution: "commander@npm:8.3.0" + checksum: 10/6b7b5d334483ce24bd73c5dac2eab901a7dbb25fd983ea24a1eeac6e7166bb1967f641546e8abf1920afbde86a45fbfe5812fbc69d0dc451bb45ca416a12a3a3 + languageName: node + linkType: hard + "commander@npm:^9.4.1": version: 9.5.0 resolution: "commander@npm:9.5.0" @@ -13946,7 +13594,7 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.33.1, core-js-compat@npm:^3.36.1": +"core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.36.1": version: 3.36.1 resolution: "core-js-compat@npm:3.36.1" dependencies: @@ -14580,58 +14228,6 @@ __metadata: languageName: node linkType: hard -"cypress@npm:9.5.1": - version: 9.5.1 - resolution: "cypress@npm:9.5.1" - dependencies: - "@cypress/request": "npm:^2.88.10" - "@cypress/xvfb": "npm:^1.2.4" - "@types/node": "npm:^14.14.31" - "@types/sinonjs__fake-timers": "npm:8.1.1" - "@types/sizzle": "npm:^2.3.2" - arch: "npm:^2.2.0" - blob-util: "npm:^2.0.2" - bluebird: "npm:^3.7.2" - buffer: "npm:^5.6.0" - cachedir: "npm:^2.3.0" - chalk: "npm:^4.1.0" - check-more-types: "npm:^2.24.0" - cli-cursor: "npm:^3.1.0" - cli-table3: "npm:~0.6.1" - commander: "npm:^5.1.0" - common-tags: "npm:^1.8.0" - dayjs: "npm:^1.10.4" - debug: "npm:^4.3.2" - enquirer: "npm:^2.3.6" - eventemitter2: "npm:^6.4.3" - execa: "npm:4.1.0" - executable: "npm:^4.1.1" - extract-zip: "npm:2.0.1" - figures: "npm:^3.2.0" - fs-extra: "npm:^9.1.0" - getos: "npm:^3.2.1" - is-ci: "npm:^3.0.0" - is-installed-globally: "npm:~0.4.0" - lazy-ass: "npm:^1.6.0" - listr2: "npm:^3.8.3" - lodash: "npm:^4.17.21" - log-symbols: "npm:^4.0.0" - minimist: "npm:^1.2.5" - ospath: "npm:^1.2.2" - pretty-bytes: "npm:^5.6.0" - proxy-from-env: "npm:1.0.0" - request-progress: "npm:^3.0.0" - semver: "npm:^7.3.2" - supports-color: "npm:^8.1.1" - tmp: "npm:~0.2.1" - untildify: "npm:^4.0.0" - yauzl: "npm:^2.10.0" - bin: - cypress: bin/cypress - checksum: 10/a5c84a1bc40ffa3e540f00b61e03caf977e7119318bebc5ac6fa0689eda4591a54f17d1b5883e016eabd3c9d704faafdf8c5aa8eee50369815606f52aa250bea - languageName: node - linkType: hard - "d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:2.5.0 - 3, d3-array@npm:3, d3-array@npm:^3.2.0": version: 3.2.2 resolution: "d3-array@npm:3.2.2" @@ -15065,13 +14661,6 @@ __metadata: languageName: node linkType: hard -"date-format@npm:^0.0.0": - version: 0.0.0 - resolution: "date-format@npm:0.0.0" - checksum: 10/5521db0145ab6fc16c43f409be0cc08bfc204a9204a442165eda0f45afc89c4c51d8aea3848d7eb68b3c998b208f67e5482ee57e7f3ec394accf21a87e2c7882 - languageName: node - linkType: hard - "dateformat@npm:^3.0.3": version: 3.0.3 resolution: "dateformat@npm:3.0.3" @@ -15116,7 +14705,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:4.3.4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -15154,13 +14743,6 @@ __metadata: languageName: node linkType: hard -"decamelize@npm:^4.0.0": - version: 4.0.0 - resolution: "decamelize@npm:4.0.0" - checksum: 10/b7d09b82652c39eead4d6678bb578e3bebd848add894b76d0f6b395bc45b2d692fb88d977e7cfb93c4ed6c119b05a1347cef261174916c2e75c0a8ca57da1809 - languageName: node - linkType: hard - "decimal.js@npm:^10.4.1": version: 10.4.2 resolution: "decimal.js@npm:10.4.2" @@ -15441,20 +15023,6 @@ __metadata: languageName: node linkType: hard -"devtools-protocol@npm:0.0.1170333": - version: 0.0.1170333 - resolution: "devtools-protocol@npm:0.0.1170333" - checksum: 10/ef33b830d27ab7e4c98931559f095987f7cb07295a12b0a7be2ac06d40388f276c0e2de51e86b9e83ba365ad0ea6d683919432d3dea7b3947f8592b369d4b489 - languageName: node - linkType: hard - -"devtools-protocol@npm:0.0.927104": - version: 0.0.927104 - resolution: "devtools-protocol@npm:0.0.927104" - checksum: 10/2f645d5d8f8df8d520c7162264ad37caed40de6c57456febb42e22cc54d527a349d05ab16f4d9f9bbd715ce425b00232b6baa66ad339bb3caa077528684ce8ca - languageName: node - linkType: hard - "diff-sequences@npm:^27.5.1": version: 27.5.1 resolution: "diff-sequences@npm:27.5.1" @@ -15469,13 +15037,6 @@ __metadata: languageName: node linkType: hard -"diff@npm:5.0.0": - version: 5.0.0 - resolution: "diff@npm:5.0.0" - checksum: 10/4a179a75b17cbb420eb9145be913f9ddb34b47cb2ba4301e80ae745122826a468f02ca8f5e56945958de26ace594899c8381acb6659c88e7803ef078b53d690c - languageName: node - linkType: hard - "diff@npm:^4.0.1": version: 4.0.2 resolution: "diff@npm:4.0.2" @@ -15881,18 +15442,7 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^4.0.0": - version: 4.5.0 - resolution: "enhanced-resolve@npm:4.5.0" - dependencies: - graceful-fs: "npm:^4.1.2" - memory-fs: "npm:^0.5.0" - tapable: "npm:^1.0.0" - checksum: 10/ae19d36c0faf6b3f66033f9e639a4358ff28c0dbb28438b1c5ab2ba04b7158fba27f4adbc4ae4116a2683b3f7063586ba8d9af2e7625fd5184ecdc83a2a0723a - languageName: node - linkType: hard - -"enhanced-resolve@npm:^5.15.0, enhanced-resolve@npm:^5.16.0": +"enhanced-resolve@npm:^5.16.0": version: 5.16.0 resolution: "enhanced-resolve@npm:5.16.0" dependencies: @@ -15962,17 +15512,6 @@ __metadata: languageName: node linkType: hard -"errno@npm:^0.1.3": - version: 0.1.8 - resolution: "errno@npm:0.1.8" - dependencies: - prr: "npm:~1.0.1" - bin: - errno: cli.js - checksum: 10/93076ed11bedb8f0389cbefcbdd3445f66443159439dccbaac89a053428ad92147676736235d275612dc0296d3f9a7e6b7177ed78a566b6cd15dacd4fa0d5888 - languageName: node - linkType: hard - "error-ex@npm:^1.3.1": version: 1.3.2 resolution: "error-ex@npm:1.3.2" @@ -16519,13 +16058,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:4.0.0, escape-string-regexp@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-string-regexp@npm:4.0.0" - checksum: 10/98b48897d93060f2322108bf29db0feba7dd774be96cd069458d1453347b25ce8682ecc39859d4bca2203cc0ab19c237bcc71755eff49a0f8d90beadeeba5cc5 - languageName: node - linkType: hard - "escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -16540,6 +16072,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10/98b48897d93060f2322108bf29db0feba7dd774be96cd069458d1453347b25ce8682ecc39859d4bca2203cc0ab19c237bcc71755eff49a0f8d90beadeeba5cc5 + languageName: node + linkType: hard + "escodegen@npm:^2.0.0, escodegen@npm:^2.1.0": version: 2.1.0 resolution: "escodegen@npm:2.1.0" @@ -17052,13 +16591,6 @@ __metadata: languageName: node linkType: hard -"eventemitter2@npm:^6.4.3": - version: 6.4.9 - resolution: "eventemitter2@npm:6.4.9" - checksum: 10/b829b1c6b11e15926b635092b5ad62b4463d1c928859831dcae606e988cf41893059e3541f5a8209d21d2f15314422ddd4d84d20830b4bf44978608d15b06b08 - languageName: node - linkType: hard - "eventemitter3@npm:5.0.1": version: 5.0.1 resolution: "eventemitter3@npm:5.0.1" @@ -17114,7 +16646,7 @@ __metadata: languageName: node linkType: hard -"execa@npm:5.1.1, execa@npm:^5.0.0, execa@npm:^5.1.1": +"execa@npm:^5.0.0, execa@npm:^5.1.1": version: 5.1.1 resolution: "execa@npm:5.1.1" dependencies: @@ -17580,13 +17112,6 @@ __metadata: languageName: node linkType: hard -"find-parent-dir@npm:~0.3.0": - version: 0.3.1 - resolution: "find-parent-dir@npm:0.3.1" - checksum: 10/da726722ca86463711589820b5dd530eb1023df550bc5e55fb27103d96a54bfced2cae6e1959bd77065384da8716676952848ace76d65323c5afc32d0c3b8363 - languageName: node - linkType: hard - "find-root@npm:^1.1.0": version: 1.1.0 resolution: "find-root@npm:1.1.0" @@ -17594,16 +17119,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:5.0.0, find-up@npm:^5.0.0": - version: 5.0.0 - resolution: "find-up@npm:5.0.0" - dependencies: - locate-path: "npm:^6.0.0" - path-exists: "npm:^4.0.0" - checksum: 10/07955e357348f34660bde7920783204ff5a26ac2cafcaa28bace494027158a97b9f56faaf2d89a6106211a8174db650dd9f503f9c0d526b1202d5554a00b9095 - languageName: node - linkType: hard - "find-up@npm:^2.0.0, find-up@npm:^2.1.0": version: 2.1.0 resolution: "find-up@npm:2.1.0" @@ -17632,6 +17147,16 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10/07955e357348f34660bde7920783204ff5a26ac2cafcaa28bace494027158a97b9f56faaf2d89a6106211a8174db650dd9f503f9c0d526b1202d5554a00b9095 + languageName: node + linkType: hard + "find-up@npm:^6.3.0": version: 6.3.0 resolution: "find-up@npm:6.3.0" @@ -18377,7 +17902,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:10.3.12, glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.2.5, glob@npm:^10.2.7, glob@npm:^10.3.10, glob@npm:^10.3.7": +"glob@npm:10.3.12, glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.2.7, glob@npm:^10.3.10, glob@npm:^10.3.7": version: 10.3.12 resolution: "glob@npm:10.3.12" dependencies: @@ -18392,20 +17917,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:7.2.0": - version: 7.2.0 - resolution: "glob@npm:7.2.0" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^3.0.4" - once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 10/bc78b6ea0735b6e23d20678aba4ae6a4760e8c9527e3c4683ac25b14e70f55f9531245dcf25959b70cbc4aa3dcce1fc37ab65fd026a4cbd70aa3a44880bd396b - languageName: node - linkType: hard - "glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -19007,23 +18518,6 @@ __metadata: languageName: node linkType: hard -"har-schema@npm:^2.0.0": - version: 2.0.0 - resolution: "har-schema@npm:2.0.0" - checksum: 10/d8946348f333fb09e2bf24cc4c67eabb47c8e1d1aa1c14184c7ffec1140a49ec8aa78aa93677ae452d71d5fc0fdeec20f0c8c1237291fc2bcb3f502a5d204f9b - languageName: node - linkType: hard - -"har-validator@npm:~5.1.3": - version: 5.1.5 - resolution: "har-validator@npm:5.1.5" - dependencies: - ajv: "npm:^6.12.3" - har-schema: "npm:^2.0.0" - checksum: 10/b998a7269ca560d7f219eedc53e2c664cd87d487e428ae854a6af4573fc94f182fe9d2e3b92ab968249baec7ebaf9ead69cf975c931dc2ab282ec182ee988280 - languageName: node - linkType: hard - "hard-rejection@npm:^2.1.0": version: 2.1.0 resolution: "hard-rejection@npm:2.1.0" @@ -19120,7 +18614,7 @@ __metadata: languageName: node linkType: hard -"he@npm:1.2.0, he@npm:^1.2.0": +"he@npm:^1.2.0": version: 1.2.0 resolution: "he@npm:1.2.0" bin: @@ -19547,17 +19041,6 @@ __metadata: languageName: node linkType: hard -"http-signature@npm:~1.2.0": - version: 1.2.0 - resolution: "http-signature@npm:1.2.0" - dependencies: - assert-plus: "npm:^1.0.0" - jsprim: "npm:^1.2.2" - sshpk: "npm:^1.7.0" - checksum: 10/2ff7112e6b0d8f08b382dfe705078c655501f2ddd76cf589d108445a9dd388a0a9be928c37108261519a7f53e6bbd1651048d74057b804807cce1ec49e87a95b - languageName: node - linkType: hard - "http-signature@npm:~1.3.6": version: 1.3.6 resolution: "http-signature@npm:1.3.6" @@ -19682,7 +19165,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24, iconv-lite@npm:^0.4.8": +"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" dependencies: @@ -19829,7 +19312,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.1, inherits@npm:~2.0.3, inherits@npm:~2.0.4": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 @@ -20402,13 +19885,6 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^2.1.0": - version: 2.1.0 - resolution: "is-plain-obj@npm:2.1.0" - checksum: 10/cec9100678b0a9fe0248a81743041ed990c2d4c99f893d935545cfbc42876cbe86d207f3b895700c690ad2fa520e568c44afc1605044b535a7820c1d40e38daa - languageName: node - linkType: hard - "is-plain-obj@npm:^3.0.0": version: 3.0.0 resolution: "is-plain-obj@npm:3.0.0" @@ -21665,18 +21141,6 @@ __metadata: languageName: node linkType: hard -"jsprim@npm:^1.2.2": - version: 1.4.2 - resolution: "jsprim@npm:1.4.2" - dependencies: - assert-plus: "npm:1.0.0" - extsprintf: "npm:1.3.0" - json-schema: "npm:0.4.0" - verror: "npm:1.10.0" - checksum: 10/df2bf234eab1b5078d01bcbff3553d50a243f7b5c10a169745efeda6344d62798bd1d85bcca6a8446f3b5d0495e989db45f9de8dae219f0f9796e70e0c776089 - languageName: node - linkType: hard - "jsprim@npm:^2.0.2": version: 2.0.2 resolution: "jsprim@npm:2.0.2" @@ -22237,7 +21701,7 @@ __metadata: languageName: node linkType: hard -"log-symbols@npm:4.1.0, log-symbols@npm:^4.0.0, log-symbols@npm:^4.1.0": +"log-symbols@npm:^4.0.0, log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" dependencies: @@ -22259,17 +21723,6 @@ __metadata: languageName: node linkType: hard -"log4js@npm:1.1.1": - version: 1.1.1 - resolution: "log4js@npm:1.1.1" - dependencies: - debug: "npm:^2.2.0" - semver: "npm:^5.3.0" - streamroller: "npm:^0.4.0" - checksum: 10/d7912f2a9718882a94297741b9770aabd1a2c12936191244312e211a94cbf44d46ffcc63ac874c096d9e3e69756077e4a0a55f7f3740e22056eddeceb0b6b1a4 - languageName: node - linkType: hard - "logfmt@npm:^1.3.2": version: 1.4.0 resolution: "logfmt@npm:1.4.0" @@ -22666,16 +22119,6 @@ __metadata: languageName: node linkType: hard -"memory-fs@npm:^0.5.0": - version: 0.5.0 - resolution: "memory-fs@npm:0.5.0" - dependencies: - errno: "npm:^0.1.3" - readable-stream: "npm:^2.0.1" - checksum: 10/5f146821d02406d031785b23e0ce4e76e751b039dbd8875e38496498dfb8bb6c0cb4b210e8d67a97af14da4c8b275c5b1a3fffdf930ec4ea35a01622e0301ecc - languageName: node - linkType: hard - "meow@npm:^13.2.0": version: 13.2.0 resolution: "meow@npm:13.2.0" @@ -22737,7 +22180,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.0, micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": version: 4.0.5 resolution: "micromatch@npm:4.0.5" dependencies: @@ -22845,15 +22288,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:5.0.1, minimatch@npm:^5.0.1": - version: 5.0.1 - resolution: "minimatch@npm:5.0.1" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10/2656580f18d9f38ada186196fcc72dc9076d70f7227adc664e72614d464e075dc4ae3936e6742519e09e336996ef33c6035e606888b12f65ca7fda792ddd2085 - languageName: node - linkType: hard - "minimatch@npm:9.0.3, minimatch@npm:^9.0.0, minimatch@npm:^9.0.1, minimatch@npm:^9.0.3": version: 9.0.3 resolution: "minimatch@npm:9.0.3" @@ -22872,6 +22306,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^5.0.1": + version: 5.0.1 + resolution: "minimatch@npm:5.0.1" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10/2656580f18d9f38ada186196fcc72dc9076d70f7227adc664e72614d464e075dc4ae3936e6742519e09e336996ef33c6035e606888b12f65ca7fda792ddd2085 + languageName: node + linkType: hard + "minimatch@npm:^8.0.2": version: 8.0.4 resolution: "minimatch@npm:8.0.4" @@ -23031,7 +22474,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.4, mkdirp@npm:^0.5.6": +"mkdirp@npm:^0.5.4, mkdirp@npm:^0.5.6": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -23128,38 +22571,6 @@ __metadata: languageName: node linkType: hard -"mocha@npm:10.2.0": - version: 10.2.0 - resolution: "mocha@npm:10.2.0" - dependencies: - ansi-colors: "npm:4.1.1" - browser-stdout: "npm:1.3.1" - chokidar: "npm:3.5.3" - debug: "npm:4.3.4" - diff: "npm:5.0.0" - escape-string-regexp: "npm:4.0.0" - find-up: "npm:5.0.0" - glob: "npm:7.2.0" - he: "npm:1.2.0" - js-yaml: "npm:4.1.0" - log-symbols: "npm:4.1.0" - minimatch: "npm:5.0.1" - ms: "npm:2.1.3" - nanoid: "npm:3.3.3" - serialize-javascript: "npm:6.0.0" - strip-json-comments: "npm:3.1.1" - supports-color: "npm:8.1.1" - workerpool: "npm:6.2.1" - yargs: "npm:16.2.0" - yargs-parser: "npm:20.2.4" - yargs-unparser: "npm:2.0.0" - bin: - _mocha: bin/_mocha - mocha: bin/mocha.js - checksum: 10/f7362898ae65e8fe716cfe62fd014b432d100c9611aaf5abe85ed14efcbfdd82f3bdf32c44bccf00c9059a264c7e8d93a69dd5b830652109052a92beffb7ea35 - languageName: node - linkType: hard - "mock-raf@npm:1.0.1": version: 1.0.1 resolution: "mock-raf@npm:1.0.1" @@ -23383,15 +22794,6 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:3.3.3": - version: 3.3.3 - resolution: "nanoid@npm:3.3.3" - bin: - nanoid: bin/nanoid.cjs - checksum: 10/c703ed58a234b68245a8a4826dd25c1453a9017d34fa28bc58e7aa8247de87d854582fa2209d7aee04084cff9ce150be8fd30300abe567dc615d4e8e735f2d99 - languageName: node - linkType: hard - "nanoid@npm:^3.3.7": version: 3.3.7 resolution: "nanoid@npm:3.3.7" @@ -24046,13 +23448,6 @@ __metadata: languageName: node linkType: hard -"oauth-sign@npm:~0.9.0": - version: 0.9.0 - resolution: "oauth-sign@npm:0.9.0" - checksum: 10/1809a366d258f41fdf4ab5310cff3d1e15f96b187503bc7333cef4351de7bd0f52cb269bc95800f1fae5fb04dd886287df1471985fd67e8484729fdbcf857119 - languageName: node - linkType: hard - "oazapfts@npm:^4.8.0": version: 4.12.0 resolution: "oazapfts@npm:4.12.0" @@ -24571,13 +23966,6 @@ __metadata: languageName: node linkType: hard -"pako@npm:^0.2.6, pako@npm:~0.2.0": - version: 0.2.9 - resolution: "pako@npm:0.2.9" - checksum: 10/627c6842e90af0b3a9ee47345bd66485a589aff9514266f4fa9318557ad819c46fedf97510f2cef9b6224c57913777966a05cb46caf6a9b31177a5401a06fe15 - languageName: node - linkType: hard - "pako@npm:^2.0.4": version: 2.0.4 resolution: "pako@npm:2.0.4" @@ -24585,6 +23973,13 @@ __metadata: languageName: node linkType: hard +"pako@npm:~0.2.0": + version: 0.2.9 + resolution: "pako@npm:0.2.9" + checksum: 10/627c6842e90af0b3a9ee47345bd66485a589aff9514266f4fa9318557ad819c46fedf97510f2cef9b6224c57913777966a05cb46caf6a9b31177a5401a06fe15 + languageName: node + linkType: hard + "papaparse@npm:5.4.1": version: 5.4.1 resolution: "papaparse@npm:5.4.1" @@ -24989,27 +24384,6 @@ __metadata: languageName: node linkType: hard -"pngjs-image@npm:~0.11.5": - version: 0.11.7 - resolution: "pngjs-image@npm:0.11.7" - dependencies: - iconv-lite: "npm:^0.4.8" - pako: "npm:^0.2.6" - pngjs: "npm:2.3.1" - request: "npm:^2.55.0" - stream-buffers: "npm:1.0.1" - underscore: "npm:1.7.0" - checksum: 10/314554f989187e6fbb376c0a5217088a91686d972a0f1932ca31b8dd3817a5d2aff2a9cd5f73135fe282bbc190032fc52fa027638b34b21e96cbbf7cbd641a90 - languageName: node - linkType: hard - -"pngjs@npm:2.3.1": - version: 2.3.1 - resolution: "pngjs@npm:2.3.1" - checksum: 10/f53b492c9c5a0586de34f8df501381013c175be0f2c04a659aca30dce08105b59d20927f2209c3fb563908180dee73082be3f5bbbfa760eee98c064baa51fe7c - languageName: node - linkType: hard - "polished@npm:^4.2.2": version: 4.2.2 resolution: "polished@npm:4.2.2" @@ -25477,16 +24851,6 @@ __metadata: languageName: node linkType: hard -"preceptor-core@npm:~0.10.0": - version: 0.10.1 - resolution: "preceptor-core@npm:0.10.1" - dependencies: - log4js: "npm:1.1.1" - underscore: "npm:1.7.0" - checksum: 10/0ae2ab8b79104cf78f798d151aba995be2ccab77a0ab1b4df9a9c653fb782166ab62c4069f4a9f639a31c395ca2c8356aaee9f78725b160093e11cf8c757d806 - languageName: node - linkType: hard - "prefix-style@npm:2.0.1": version: 2.0.1 resolution: "prefix-style@npm:2.0.1" @@ -25638,15 +25002,6 @@ __metadata: languageName: node linkType: hard -"promise@npm:6.0.0": - version: 6.0.0 - resolution: "promise@npm:6.0.0" - dependencies: - asap: "npm:~1.0.0" - checksum: 10/0801d3d23ddc6f98936fc52a220379b9c27b9c0c56361cb10f1de8ce9927a28b71581f137228ddbf9b6fbaeebefc267a1b9166ac99318a33cc660e9cabc37a7a - languageName: node - linkType: hard - "prompts@npm:^2.0.1, prompts@npm:^2.4.0": version: 2.4.2 resolution: "prompts@npm:2.4.2" @@ -25753,13 +25108,6 @@ __metadata: languageName: node linkType: hard -"prr@npm:~1.0.1": - version: 1.0.1 - resolution: "prr@npm:1.0.1" - checksum: 10/3bca2db0479fd38f8c4c9439139b0c42dcaadcc2fbb7bb8e0e6afaa1383457f1d19aea9e5f961d5b080f1cfc05bfa1fe9e45c97a1d3fd6d421950a73d3108381 - languageName: node - linkType: hard - "pseudoizer@npm:^0.1.0": version: 0.1.0 resolution: "pseudoizer@npm:0.1.0" @@ -25767,7 +25115,7 @@ __metadata: languageName: node linkType: hard -"psl@npm:^1.1.28, psl@npm:^1.1.33": +"psl@npm:^1.1.33": version: 1.9.0 resolution: "psl@npm:1.9.0" checksum: 10/d07879d4bfd0ac74796306a8e5a36a93cfb9c4f4e8ee8e63fbb909066c192fe1008cd8f12abd8ba2f62ca28247949a20c8fb32e1d18831d9e71285a1569720f9 @@ -25844,7 +25192,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.10.4, qs@npm:~6.10.3": +"qs@npm:6.10.4": version: 6.10.4 resolution: "qs@npm:6.10.4" dependencies: @@ -25871,13 +25219,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:~6.5.2": - version: 6.5.3 - resolution: "qs@npm:6.5.3" - checksum: 10/485c990fba7ad17671e16c92715fb064c1600337738f5d140024eb33a49fbc1ed31890d3db850117c760caeb9c9cc9f4ba22a15c20dd119968e41e3d3fe60b28 - languageName: node - linkType: hard - "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -27300,18 +26641,6 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^1.1.7": - version: 1.1.14 - resolution: "readable-stream@npm:1.1.14" - dependencies: - core-util-is: "npm:~1.0.0" - inherits: "npm:~2.0.1" - isarray: "npm:0.0.1" - string_decoder: "npm:~0.10.x" - checksum: 10/1aa2cf4bd02f9ab3e1d57842a43a413b52be5300aa089ad1f2e3cea00684532d73edc6a2ba52b0c3210d8b57eb20a695a6d2b96d1c6085ee979c6021ad48ad20 - languageName: node - linkType: hard - "readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.6, readable-stream@npm:^2.2.2, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" @@ -27621,34 +26950,6 @@ __metadata: languageName: node linkType: hard -"request@npm:^2.55.0": - version: 2.88.2 - resolution: "request@npm:2.88.2" - dependencies: - aws-sign2: "npm:~0.7.0" - aws4: "npm:^1.8.0" - caseless: "npm:~0.12.0" - combined-stream: "npm:~1.0.6" - extend: "npm:~3.0.2" - forever-agent: "npm:~0.6.1" - form-data: "npm:~2.3.2" - har-validator: "npm:~5.1.3" - http-signature: "npm:~1.2.0" - is-typedarray: "npm:~1.0.0" - isstream: "npm:~0.1.2" - json-stringify-safe: "npm:~5.0.1" - mime-types: "npm:~2.1.19" - oauth-sign: "npm:~0.9.0" - performance-now: "npm:^2.1.0" - qs: "npm:~6.5.2" - safe-buffer: "npm:^5.1.2" - tough-cookie: "npm:~2.5.0" - tunnel-agent: "npm:^0.6.0" - uuid: "npm:^3.3.2" - checksum: 10/005b8b237b56f1571cfd4ecc09772adaa2e82dcb884fc14ea2bb25e23dbf7c2009f9929e0b6d3fd5802e33ed8ee705a3b594c8f9467c1458cd973872bf89db8e - languageName: node - linkType: hard - "require-directory@npm:^2.1.1": version: 2.1.1 resolution: "require-directory@npm:2.1.1" @@ -27684,15 +26985,6 @@ __metadata: languageName: node linkType: hard -"resolve-bin@npm:1.0.1": - version: 1.0.1 - resolution: "resolve-bin@npm:1.0.1" - dependencies: - find-parent-dir: "npm:~0.3.0" - checksum: 10/439678433138cbea49b848be68ae24aa2afa1b3bb82c30fa763ebbc5615e8c340debfe635ee902cdbb4be72528e4139e2adb0503370ee755ad3702d03976af4c - languageName: node - linkType: hard - "resolve-cwd@npm:^3.0.0": version: 3.0.0 resolution: "resolve-cwd@npm:3.0.0" @@ -27852,17 +27144,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:5.0.1": - version: 5.0.1 - resolution: "rimraf@npm:5.0.1" - dependencies: - glob: "npm:^10.2.5" - bin: - rimraf: dist/cjs/src/bin.js - checksum: 10/0691e4d7482f2de2af8628976413e146cd6b204f52ab88be91a3bb69cb9c8669ee795ef7c1e964e8ec6bfeaec0212326287d53ec3eef26dac406c8f19c97f0c4 - languageName: node - linkType: hard - "rimraf@npm:5.0.5, rimraf@npm:^5.0.5": version: 5.0.5 resolution: "rimraf@npm:5.0.5" @@ -28311,7 +27592,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.3.0, semver@npm:^5.6.0": +"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.6.0": version: 5.7.2 resolution: "semver@npm:5.7.2" bin: @@ -28320,7 +27601,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.6.0, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": +"semver@npm:7.6.0, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": version: 7.6.0 resolution: "semver@npm:7.6.0" dependencies: @@ -28361,15 +27642,6 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:6.0.0": - version: 6.0.0 - resolution: "serialize-javascript@npm:6.0.0" - dependencies: - randombytes: "npm:^2.1.0" - checksum: 10/ed3dabfbb565c48c9eb1ca8fe58f0d256902ab70a8a605be634ddd68388d5f728bb0bd1268e94fab628748ba8ad8392f01b05f3cbe1e4878b5c58c669fd3d1b4 - languageName: node - linkType: hard - "serialize-javascript@npm:^6.0.1, serialize-javascript@npm:^6.0.2": version: 6.0.2 resolution: "serialize-javascript@npm:6.0.2" @@ -29154,7 +28426,7 @@ __metadata: languageName: node linkType: hard -"sshpk@npm:^1.14.1, sshpk@npm:^1.7.0": +"sshpk@npm:^1.14.1": version: 1.17.0 resolution: "sshpk@npm:1.17.0" dependencies: @@ -29354,13 +28626,6 @@ __metadata: languageName: node linkType: hard -"stream-buffers@npm:1.0.1": - version: 1.0.1 - resolution: "stream-buffers@npm:1.0.1" - checksum: 10/df6e4f5004ccae2ec52bec81ec910204508e5cc4830d3aaac2979e481ee0bba2fda13636d9b63ce594a35f3d9b66d97eeb84214a25f217bb31d651664835d130 - languageName: node - linkType: hard - "stream-composer@npm:^1.0.2": version: 1.0.2 resolution: "stream-composer@npm:1.0.2" @@ -29377,18 +28642,6 @@ __metadata: languageName: node linkType: hard -"streamroller@npm:^0.4.0": - version: 0.4.1 - resolution: "streamroller@npm:0.4.1" - dependencies: - date-format: "npm:^0.0.0" - debug: "npm:^0.7.2" - mkdirp: "npm:^0.5.1" - readable-stream: "npm:^1.1.7" - checksum: 10/a546d4905478c6aa32edffa970a485b641358e1a4b26b99a3caee05ca8054dd90eefdc0eac19f3a35a1984f9747dc35cd26f346cba084d2ee80c8844f9d7090b - languageName: node - linkType: hard - "streamx@npm:^2.12.0, streamx@npm:^2.12.5, streamx@npm:^2.13.2, streamx@npm:^2.14.0": version: 2.15.7 resolution: "streamx@npm:2.15.7" @@ -29525,13 +28778,6 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:~0.10.x": - version: 0.10.31 - resolution: "string_decoder@npm:0.10.31" - checksum: 10/cc43e6b1340d4c7843da0e37d4c87a4084c2342fc99dcf6563c3ec273bb082f0cbd4ebf25d5da19b04fb16400d393885fda830be5128e1c416c73b5a6165f175 - languageName: node - linkType: hard - "string_decoder@npm:~1.1.1": version: 1.1.1 resolution: "string_decoder@npm:1.1.1" @@ -29598,7 +28844,7 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:3.1.1, strip-json-comments@npm:^3.0.1, strip-json-comments@npm:^3.1.1": +"strip-json-comments@npm:^3.0.1, strip-json-comments@npm:^3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" checksum: 10/492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443 @@ -29739,15 +28985,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:8.1.1, supports-color@npm:^8.0.0, supports-color@npm:^8.1.1": - version: 8.1.1 - resolution: "supports-color@npm:8.1.1" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10/157b534df88e39c5518c5e78c35580c1eca848d7dbaf31bbe06cdfc048e22c7ff1a9d046ae17b25691128f631a51d9ec373c1b740c12ae4f0de6e292037e4282 - languageName: node - linkType: hard - "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -29766,6 +29003,15 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10/157b534df88e39c5518c5e78c35580c1eca848d7dbaf31bbe06cdfc048e22c7ff1a9d046ae17b25691128f631a51d9ec373c1b740c12ae4f0de6e292037e4282 + languageName: node + linkType: hard + "supports-hyperlinks@npm:^3.0.0": version: 3.0.0 resolution: "supports-hyperlinks@npm:3.0.0" @@ -29918,13 +29164,6 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^1.0.0": - version: 1.1.3 - resolution: "tapable@npm:1.1.3" - checksum: 10/1cec71f00f9a6cb1d88961b5d4f2dead4e185508b18b1bf1e688c8135039a391dd3e12b0887232b682ef28f1ef6f0c5e9a48794f6f5ef68f35d05de7e7a0a578 - languageName: node - linkType: hard - "tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": version: 2.2.1 resolution: "tapable@npm:2.2.1" @@ -30039,7 +29278,7 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:5.3.10, terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.10, terser-webpack-plugin@npm:^5.3.7": +"terser-webpack-plugin@npm:5.3.10, terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.10": version: 5.3.10 resolution: "terser-webpack-plugin@npm:5.3.10" dependencies: @@ -30329,16 +29568,6 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:~2.5.0": - version: 2.5.0 - resolution: "tough-cookie@npm:2.5.0" - dependencies: - psl: "npm:^1.1.28" - punycode: "npm:^2.1.1" - checksum: 10/024cb13a4d1fe9af57f4323dff765dd9b217cc2a69be77e3b8a1ca45600aa33a097b6ad949f225d885e904f4bd3ceccef104741ef202d8378e6ca78e850ff82f - languageName: node - linkType: hard - "tr46@npm:^3.0.0": version: 3.0.0 resolution: "tr46@npm:3.0.0" @@ -30443,22 +29672,6 @@ __metadata: languageName: node linkType: hard -"ts-loader@npm:8.4.0": - version: 8.4.0 - resolution: "ts-loader@npm:8.4.0" - dependencies: - chalk: "npm:^4.1.0" - enhanced-resolve: "npm:^4.0.0" - loader-utils: "npm:^2.0.0" - micromatch: "npm:^4.0.0" - semver: "npm:^7.3.4" - peerDependencies: - typescript: "*" - webpack: "*" - checksum: 10/d5cd87c1070840e0502ca8f348c385980a159efbe2d2d24cc259a4063b562c9e1c3e38c7b150f8aabeee3097132aca20a60eced335037631cb25e3f3f906ea0c - languageName: node - linkType: hard - "ts-node@npm:10.9.2, ts-node@npm:^10.2.1": version: 10.9.2 resolution: "ts-node@npm:10.9.2" @@ -30840,13 +30053,6 @@ __metadata: languageName: node linkType: hard -"underscore@npm:1.13.6": - version: 1.13.6 - resolution: "underscore@npm:1.13.6" - checksum: 10/58cf5dc42cb0ac99c146ae4064792c0a2cc84f3a3c4ad88f5082e79057dfdff3371d896d1ec20379e9ece2450d94fa78f2ef5bfefc199ba320653e32c009bd66 - languageName: node - linkType: hard - "undici-types@npm:~5.26.4": version: 5.26.5 resolution: "undici-types@npm:5.26.5" @@ -31208,15 +30414,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:9.0.0": - version: 9.0.0 - resolution: "uuid@npm:9.0.0" - bin: - uuid: dist/bin/uuid - checksum: 10/23857699a616d1b48224bc2b8440eae6e57d25463c3a0200e514ba8279dfa3bde7e92ea056122237839cfa32045e57d8f8f4a30e581d720fd72935572853ae2e - languageName: node - linkType: hard - "uuid@npm:9.0.1, uuid@npm:^9.0.0, uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" @@ -31226,15 +30423,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^3.3.2": - version: 3.4.0 - resolution: "uuid@npm:3.4.0" - bin: - uuid: ./bin/uuid - checksum: 10/4f2b86432b04cc7c73a0dd1bcf11f1fc18349d65d2e4e32dd0fc658909329a1e0cc9244aa93f34c0cccfdd5ae1af60a149251a5f420ec3ac4223a3dab198fb2e - languageName: node - linkType: hard - "uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" @@ -31458,7 +30646,7 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.2.0, watchpack@npm:^2.4.0, watchpack@npm:^2.4.1": +"watchpack@npm:^2.2.0, watchpack@npm:^2.4.1": version: 2.4.1 resolution: "watchpack@npm:2.4.1" dependencies: @@ -31774,43 +30962,6 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5.89.0": - version: 5.89.0 - resolution: "webpack@npm:5.89.0" - dependencies: - "@types/eslint-scope": "npm:^3.7.3" - "@types/estree": "npm:^1.0.0" - "@webassemblyjs/ast": "npm:^1.11.5" - "@webassemblyjs/wasm-edit": "npm:^1.11.5" - "@webassemblyjs/wasm-parser": "npm:^1.11.5" - acorn: "npm:^8.7.1" - acorn-import-assertions: "npm:^1.9.0" - browserslist: "npm:^4.14.5" - chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.15.0" - es-module-lexer: "npm:^1.2.1" - eslint-scope: "npm:5.1.1" - events: "npm:^3.2.0" - glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.2.9" - json-parse-even-better-errors: "npm:^2.3.1" - loader-runner: "npm:^4.2.0" - mime-types: "npm:^2.1.27" - neo-async: "npm:^2.6.2" - schema-utils: "npm:^3.2.0" - tapable: "npm:^2.1.1" - terser-webpack-plugin: "npm:^5.3.7" - watchpack: "npm:^2.4.0" - webpack-sources: "npm:^3.2.3" - peerDependenciesMeta: - webpack-cli: - optional: true - bin: - webpack: bin/webpack.js - checksum: 10/ee19b070279c9bc3bf21eeaac3ea08e6583c1b8da334e595b3c9badedbd7f9fad071b9f785076081af661ef247bb72441e86e8b903bf253ae9300007a048ea6e - languageName: node - linkType: hard - "webpackbar@npm:^6.0.0": version: 6.0.1 resolution: "webpackbar@npm:6.0.1" @@ -32004,13 +31155,6 @@ __metadata: languageName: node linkType: hard -"workerpool@npm:6.2.1": - version: 6.2.1 - resolution: "workerpool@npm:6.2.1" - checksum: 10/3e637f76320cab92eaeffa4fbefb351db02e20aa29245d8ee05fa7c088780ef7b4446bfafff2668a22fd94b7d9d97c7020117036ac77a76370ecea278b9a9b91 - languageName: node - linkType: hard - "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" @@ -32248,13 +31392,6 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:20.2.4": - version: 20.2.4 - resolution: "yargs-parser@npm:20.2.4" - checksum: 10/db8f251ae40e24782d5c089ed86883ba3c0ce7f3c174002a67ec500802f928df9d505fea5d04829769221ce20b0f69f6fb1138fbb2e2fb102e3e9d426d20edab - languageName: node - linkType: hard - "yargs-parser@npm:21.1.1, yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" @@ -32269,33 +31406,6 @@ __metadata: languageName: node linkType: hard -"yargs-unparser@npm:2.0.0": - version: 2.0.0 - resolution: "yargs-unparser@npm:2.0.0" - dependencies: - camelcase: "npm:^6.0.0" - decamelize: "npm:^4.0.0" - flat: "npm:^5.0.2" - is-plain-obj: "npm:^2.1.0" - checksum: 10/68f9a542c6927c3768c2f16c28f71b19008710abd6b8f8efbac6dcce26bbb68ab6503bed1d5994bdbc2df9a5c87c161110c1dfe04c6a3fe5c6ad1b0e15d9a8a3 - languageName: node - linkType: hard - -"yargs@npm:16.2.0, yargs@npm:^16.2.0": - version: 16.2.0 - resolution: "yargs@npm:16.2.0" - dependencies: - cliui: "npm:^7.0.2" - escalade: "npm:^3.1.1" - get-caller-file: "npm:^2.0.5" - require-directory: "npm:^2.1.1" - string-width: "npm:^4.2.0" - y18n: "npm:^5.0.5" - yargs-parser: "npm:^20.2.2" - checksum: 10/807fa21211d2117135d557f95fcd3c3d390530cda2eca0c840f1d95f0f40209dcfeb5ec18c785a1f3425896e623e3b2681e8bb7b6600060eda1c3f4804e7957e - languageName: node - linkType: hard - "yargs@npm:17.7.2, yargs@npm:^17.0.1, yargs@npm:^17.3.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" @@ -32311,6 +31421,21 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^16.2.0": + version: 16.2.0 + resolution: "yargs@npm:16.2.0" + dependencies: + cliui: "npm:^7.0.2" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.0" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^20.2.2" + checksum: 10/807fa21211d2117135d557f95fcd3c3d390530cda2eca0c840f1d95f0f40209dcfeb5ec18c785a1f3425896e623e3b2681e8bb7b6600060eda1c3f4804e7957e + languageName: node + linkType: hard + "yauzl@npm:^2.10.0": version: 2.10.0 resolution: "yauzl@npm:2.10.0" From 8ca10cfe78fb0d51eb2130b42e105171a5cfe504 Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Thu, 25 Apr 2024 01:31:29 -0400 Subject: [PATCH 099/222] Revert "ci: remove milestone check" (#86856) Revert "ci: remove milestone check (#86452)" This reverts commit 4bb0b78abbeb9bfa997ea837dfac08a852c6a4e4. --- .github/pr-checks.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/pr-checks.json b/.github/pr-checks.json index 3566f6a65df..b97dbc137ec 100644 --- a/.github/pr-checks.json +++ b/.github/pr-checks.json @@ -1,4 +1,11 @@ [ + { + "type": "check-milestone", + "title": "Milestone Check", + "targetUrl": "https://github.com/grafana/grafana/blob/main/contribute/merge-pull-request.md#assign-a-milestone", + "success": "Milestone set", + "failure": "Milestone not set" + }, { "type": "check-changelog", "title": "Changelog Check", From 4ff5a561af20a34b105f542846b9e4ab557440c1 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 25 Apr 2024 08:20:02 +0200 Subject: [PATCH 100/222] Fix: Update lockfile (#86901) update lockfile --- yarn.lock | 231 ------------------------------------------------------ 1 file changed, 231 deletions(-) diff --git a/yarn.lock b/yarn.lock index e1e9fa786a9..e541abbec12 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2215,13 +2215,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/android-arm64@npm:0.18.12" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/android-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm64@npm:0.18.20" @@ -2243,13 +2236,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/android-arm@npm:0.18.12" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@esbuild/android-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm@npm:0.18.20" @@ -2271,13 +2257,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/android-x64@npm:0.18.12" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - "@esbuild/android-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-x64@npm:0.18.20" @@ -2299,13 +2278,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/darwin-arm64@npm:0.18.12" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/darwin-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-arm64@npm:0.18.20" @@ -2327,13 +2299,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/darwin-x64@npm:0.18.12" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@esbuild/darwin-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-x64@npm:0.18.20" @@ -2355,13 +2320,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/freebsd-arm64@npm:0.18.12" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/freebsd-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-arm64@npm:0.18.20" @@ -2383,13 +2341,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/freebsd-x64@npm:0.18.12" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/freebsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-x64@npm:0.18.20" @@ -2411,13 +2362,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/linux-arm64@npm:0.18.12" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/linux-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm64@npm:0.18.20" @@ -2439,13 +2383,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/linux-arm@npm:0.18.12" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@esbuild/linux-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm@npm:0.18.20" @@ -2467,13 +2404,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/linux-ia32@npm:0.18.12" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/linux-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ia32@npm:0.18.20" @@ -2495,13 +2425,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/linux-loong64@npm:0.18.12" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - "@esbuild/linux-loong64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-loong64@npm:0.18.20" @@ -2523,13 +2446,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/linux-mips64el@npm:0.18.12" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - "@esbuild/linux-mips64el@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-mips64el@npm:0.18.20" @@ -2551,13 +2467,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/linux-ppc64@npm:0.18.12" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/linux-ppc64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ppc64@npm:0.18.20" @@ -2579,13 +2488,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/linux-riscv64@npm:0.18.12" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - "@esbuild/linux-riscv64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-riscv64@npm:0.18.20" @@ -2607,13 +2509,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/linux-s390x@npm:0.18.12" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - "@esbuild/linux-s390x@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-s390x@npm:0.18.20" @@ -2635,13 +2530,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/linux-x64@npm:0.18.12" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - "@esbuild/linux-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-x64@npm:0.18.20" @@ -2663,13 +2551,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/netbsd-x64@npm:0.18.12" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/netbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/netbsd-x64@npm:0.18.20" @@ -2691,13 +2572,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/openbsd-x64@npm:0.18.12" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/openbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/openbsd-x64@npm:0.18.20" @@ -2719,13 +2593,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/sunos-x64@npm:0.18.12" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - "@esbuild/sunos-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/sunos-x64@npm:0.18.20" @@ -2747,13 +2614,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/win32-arm64@npm:0.18.12" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/win32-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-arm64@npm:0.18.20" @@ -2775,13 +2635,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/win32-ia32@npm:0.18.12" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/win32-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-ia32@npm:0.18.20" @@ -2803,13 +2656,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.18.12": - version: 0.18.12 - resolution: "@esbuild/win32-x64@npm:0.18.12" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@esbuild/win32-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-x64@npm:0.18.20" @@ -15733,83 +15579,6 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:0.18.12": - version: 0.18.12 - resolution: "esbuild@npm:0.18.12" - dependencies: - "@esbuild/android-arm": "npm:0.18.12" - "@esbuild/android-arm64": "npm:0.18.12" - "@esbuild/android-x64": "npm:0.18.12" - "@esbuild/darwin-arm64": "npm:0.18.12" - "@esbuild/darwin-x64": "npm:0.18.12" - "@esbuild/freebsd-arm64": "npm:0.18.12" - "@esbuild/freebsd-x64": "npm:0.18.12" - "@esbuild/linux-arm": "npm:0.18.12" - "@esbuild/linux-arm64": "npm:0.18.12" - "@esbuild/linux-ia32": "npm:0.18.12" - "@esbuild/linux-loong64": "npm:0.18.12" - "@esbuild/linux-mips64el": "npm:0.18.12" - "@esbuild/linux-ppc64": "npm:0.18.12" - "@esbuild/linux-riscv64": "npm:0.18.12" - "@esbuild/linux-s390x": "npm:0.18.12" - "@esbuild/linux-x64": "npm:0.18.12" - "@esbuild/netbsd-x64": "npm:0.18.12" - "@esbuild/openbsd-x64": "npm:0.18.12" - "@esbuild/sunos-x64": "npm:0.18.12" - "@esbuild/win32-arm64": "npm:0.18.12" - "@esbuild/win32-ia32": "npm:0.18.12" - "@esbuild/win32-x64": "npm:0.18.12" - dependenciesMeta: - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10/724411d6dc01ac4ee9a6a7f9f735765f81383c9f2a1cbc35dcccf558cd5bc13f0913ff9a3a3eab0dfe3db20f85991e9f60f59a5d709443ed2179d525722d17e9 - languageName: node - linkType: hard - "esbuild@npm:0.20.2, esbuild@npm:^0.20.0, esbuild@npm:^0.20.1": version: 0.20.2 resolution: "esbuild@npm:0.20.2" From 0140dfdddf8ec25b0d239bcdc78d275ffc19be33 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 25 Apr 2024 10:00:11 +0200 Subject: [PATCH 101/222] Chore: Add e2e selectors to alert rule page (#86909) wip --- .../src/selectors/components.ts | 9 +++++++++ .../rule-editor/AlertRuleNameInput.tsx | 2 ++ .../components/rule-editor/FolderAndGroup.tsx | 18 ++++++++++++++++-- .../QueryAndExpressionsStep.tsx | 8 +++++++- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 7dd6dd3ecb3..a2142e464fb 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -300,6 +300,15 @@ export const Components = { groupToggle: 'data-testid group-collapse-toggle', toggle: 'data-testid collapse-toggle', expandedContent: 'data-testid expanded-content', + previewButton: 'data-testid alert-rule preview-button', + ruleNameField: 'data-testid alert-rule name-field', + newFolderButton: 'data-testid alert-rule new-folder-button', + newFolderNameField: 'data-testid alert-rule name-folder-name-field', + newFolderNameCreateButton: 'data-testid alert-rule name-folder-name-create-button', + newEvaluationGroupButton: 'data-testid alert-rule new-evaluation-group-button', + newEvaluationGroupName: 'data-testid alert-rule new-evaluation-group-name', + newEvaluationGroupInterval: 'data-testid alert-rule new-evaluation-group-interval', + newEvaluationGroupCreate: 'data-testid alert-rule new-evaluation-group-create-button', }, Alert: { /** diff --git a/public/app/features/alerting/unified/components/rule-editor/AlertRuleNameInput.tsx b/public/app/features/alerting/unified/components/rule-editor/AlertRuleNameInput.tsx index f96d308e08e..ffa4a09b4b1 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AlertRuleNameInput.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AlertRuleNameInput.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { useFormContext } from 'react-hook-form'; +import { selectors } from '@grafana/e2e-selectors'; import { Field, Input, Text } from '@grafana/ui'; import { RuleFormType, RuleFormValues } from '../../types/rule-form'; @@ -35,6 +36,7 @@ export const AlertRuleNameInput = () => { > New folder @@ -258,6 +260,7 @@ export function FolderAndGroup({ fill="outline" variant="secondary" disabled={!folder} + data-testid={selectors.components.AlertRules.newEvaluationGroupButton} > New evaluation group @@ -309,6 +312,7 @@ function FolderCreationModal({ invalid={error} > Cancel - @@ -388,6 +396,7 @@ function EvaluationGroupCreationModal({ invalid={Boolean(formState.errors.group)} > Cancel - diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx index 9a40a1a3d55..ef70b82adc0 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx @@ -497,7 +497,13 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P )} {!isPreviewLoading && ( - )} From 1c121ff764895ab94623d9abe31b25682367c203 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 25 Apr 2024 11:04:45 +0200 Subject: [PATCH 102/222] Units: add test cases for siprefix (milli, micro, etc) (#86695) --- .../grafana-data/src/valueFormats/symbolFormatters.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/grafana-data/src/valueFormats/symbolFormatters.test.ts b/packages/grafana-data/src/valueFormats/symbolFormatters.test.ts index 05273473e84..e5a926333fb 100644 --- a/packages/grafana-data/src/valueFormats/symbolFormatters.test.ts +++ b/packages/grafana-data/src/valueFormats/symbolFormatters.test.ts @@ -63,6 +63,12 @@ describe('SIPrefix', () => { it.each` value | expectedSuffix | expectedText ${0} | ${' V'} | ${'0'} + ${0.000000000001} | ${' pV'} | ${'1'} + ${0.000000001} | ${' nV'} | ${'1'} + ${0.000001} | ${' µV'} | ${'1'} + ${0.001} | ${' mV'} | ${'1'} + ${0.999} | ${' mV'} | ${'999'} + ${1} | ${' V'} | ${'1'} ${999} | ${' V'} | ${'999'} ${1000} | ${' kV'} | ${'1'} ${1000000} | ${' MV'} | ${'1'} From c049e5bbfc0903a91dcb8d88205fabdff74888bc Mon Sep 17 00:00:00 2001 From: George Robinson Date: Thu, 25 Apr 2024 10:47:01 +0100 Subject: [PATCH 103/222] Alerting: Update grafana/alerting to bb4f4f4 (#86827) --- .../template-notifications/reference.md | 13 +- go.mod | 19 +- go.sum | 36 +-- go.work | 4 + go.work.sum | 205 ++++++++++++++++++ pkg/apimachinery/go.mod | 2 +- pkg/apimachinery/go.sum | 3 +- pkg/apiserver/go.mod | 13 +- pkg/apiserver/go.sum | 21 +- pkg/build/wire/go.mod | 2 +- pkg/build/wire/go.sum | 10 +- pkg/promlib/go.mod | 14 +- pkg/promlib/go.sum | 21 +- 13 files changed, 283 insertions(+), 80 deletions(-) diff --git a/docs/sources/alerting/configure-notifications/template-notifications/reference.md b/docs/sources/alerting/configure-notifications/template-notifications/reference.md index d38e517a4d2..3bb4fe3f216 100644 --- a/docs/sources/alerting/configure-notifications/template-notifications/reference.md +++ b/docs/sources/alerting/configure-notifications/template-notifications/reference.md @@ -74,10 +74,19 @@ In addition to iterating over each key value pair, you can sort the pairs, remov ### Time -Time is from the Go [`time`](https://pkg.go.dev/time#Time) package. You can print a time in a number of different formats. For example, to print the time that an alert fired in the format `Monday, 1st January 2022 at 10:00AM` you would write the following template: +Time is from the Go [`time`](https://pkg.go.dev/time#Time) package. + +You can format a time in a number of different formats using the `date` function. +For example, to print the time that an alert fired in the format `15:04:05 MST`: ``` -{{ .StartsAt.Format "Monday, 2 January 2006 at 3:04PM" }} +{{ .StartsAt | date "15:04:05 MST" }} +``` + +You can also use the `tz` function to change the timezone from UTC to a local time. For example: + +``` +{{ .StartsAt | tz "Europe/Paris" | date "15:04:05 MST" }} ``` You can find a reference for Go's time format [here](https://pkg.go.dev/time#pkg-constants). diff --git a/go.mod b/go.mod index 741049a26d9..93830c46345 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/BurntSushi/toml v1.3.2 // @grafana/identity-access-team github.com/Masterminds/semver v1.5.0 // @grafana/grafana-backend-group github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // @grafana/grafana-backend-group - github.com/aws/aws-sdk-go v1.50.8 // @grafana/aws-datasources + github.com/aws/aws-sdk-go v1.50.29 // @grafana/aws-datasources github.com/beevik/etree v1.2.0 // @grafana/grafana-backend-group github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-squad-backend github.com/blang/semver/v4 v4.0.0 // @grafana/grafana-release-guild @@ -47,7 +47,7 @@ require ( github.com/google/uuid v1.6.0 // @grafana/grafana-backend-group github.com/google/wire v0.5.0 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20240409171830-e039a7f57a92 // @grafana/alerting-squad-backend + github.com/grafana/alerting v0.0.0-20240424080142-bb4f4f429d36 // @grafana/alerting-squad-backend github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code github.com/grafana/grafana-aws-sdk v0.25.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.0.1 // @grafana/partner-datasources @@ -74,9 +74,9 @@ require ( github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/alertmanager v0.26.0 // @grafana/alerting-squad-backend - github.com/prometheus/client_golang v1.18.0 // @grafana/alerting-squad-backend - github.com/prometheus/client_model v0.5.0 // @grafana/grafana-backend-group - github.com/prometheus/common v0.46.0 // @grafana/alerting-squad-backend + github.com/prometheus/client_golang v1.19.0 // @grafana/alerting-squad-backend + github.com/prometheus/client_model v0.6.0 // @grafana/grafana-backend-group + github.com/prometheus/common v0.48.0 // @grafana/alerting-squad-backend github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 // @grafana/alerting-squad-backend github.com/robfig/cron/v3 v3.0.1 // @grafana/grafana-backend-group github.com/russellhaering/goxmldsig v1.4.0 // @grafana/grafana-backend-group @@ -99,7 +99,7 @@ require ( golang.org/x/oauth2 v0.19.0 // @grafana/identity-access-team golang.org/x/sync v0.6.0 // @grafana/alerting-squad-backend golang.org/x/time v0.5.0 // @grafana/grafana-backend-group - golang.org/x/tools v0.17.0 // @grafana/grafana-as-code + golang.org/x/tools v0.18.0 // @grafana/grafana-as-code gonum.org/v1/gonum v0.12.0 // @grafana/observability-metrics google.golang.org/api v0.176.0 // @grafana/grafana-backend-group google.golang.org/grpc v1.63.2 // @grafana/plugins-platform-backend @@ -247,7 +247,7 @@ require ( github.com/microsoft/go-mssqldb v1.6.1-0.20240214161942-b65008136246 // @grafana/grafana-bi-squad github.com/redis/go-redis/v9 v9.1.0 // @grafana/alerting-squad-backend go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0 // @grafana/grafana-backend-group - golang.org/x/mod v0.14.0 // @grafana/grafana-backend-group + golang.org/x/mod v0.15.0 // @grafana/grafana-backend-group k8s.io/utils v0.0.0-20230726121419-3b25d923346b // @grafana/partner-datasources ) @@ -261,7 +261,7 @@ require ( k8s.io/component-base v0.29.2 // @grafana/grafana-app-platform-squad k8s.io/klog/v2 v2.120.1 // @grafana/grafana-app-platform-squad k8s.io/kube-aggregator v0.29.0 // @grafana/grafana-app-platform-squad - k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5 // @grafana/grafana-app-platform-squad + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // @grafana/grafana-app-platform-squad ) require github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad @@ -481,6 +481,7 @@ require ( github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.9.1 // indirect + github.com/go-logr/zapr v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect @@ -514,7 +515,7 @@ replace github.com/hashicorp/go-hclog => github.com/hashicorp/go-hclog v0.16.1 // Use our fork of the upstream alertmanagers. // This is required in order to get notification delivery errors from the receivers API. -replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20240321101410-40158de684b2 +replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible diff --git a/go.sum b/go.sum index e68ddd110ba..0f90c95e311 100644 --- a/go.sum +++ b/go.sum @@ -1399,8 +1399,8 @@ github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.48.14/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/aws/aws-sdk-go v1.50.8 h1:gY0WoOW+/Wz6XmYSgDH9ge3wnAevYDSQWPxxJvqAkP4= -github.com/aws/aws-sdk-go v1.50.8/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.29 h1:Ol2FYzesF2tsQrgVSnDWRFI60+FsSqKKdt7MLlZKubc= +github.com/aws/aws-sdk-go v1.50.29/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= github.com/aws/aws-sdk-go-v2 v1.16.2 h1:fqlCk6Iy3bnCumtrLz9r3mJ/2gUT0pJ0wLFVIdWh+JA= @@ -1801,8 +1801,8 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= github.com/go-openapi/analysis v0.21.5/go.mod h1:25YcZosX9Lwz2wBsrFrrsL8bmjjXdlyP6zsr2AMy29M= @@ -2166,8 +2166,8 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20240409171830-e039a7f57a92 h1:DfzT76QLAyvJZiQMEnHTRRkQsvOF0S2jH9bO1OIX5yw= -github.com/grafana/alerting v0.0.0-20240409171830-e039a7f57a92/go.mod h1:jriidrGFnZWvAn3xw/BOoCkJbGWnncuojMi9jq6xxwE= +github.com/grafana/alerting v0.0.0-20240424080142-bb4f4f429d36 h1:v4aQ0cde8SCzNRrD2RczzmFolEkXWriSY9tKakAD0ng= +github.com/grafana/alerting v0.0.0-20240424080142-bb4f4f429d36/go.mod h1:8nOsn7PWmttOmWiR7bvYIl3VLl+tIq72ZF+1y54w36M= github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 h1:lGEuhD/KhhN1OiPrvwQejl9Lg8MvaHdj3lHZNref4is= github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4= github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw= @@ -2203,8 +2203,8 @@ github.com/grafana/grafana/pkg/promlib v0.0.5 h1:LiJP4ZPdk4qsgvrcw+0DoW0/+RjAND/ github.com/grafana/grafana/pkg/promlib v0.0.5/go.mod h1:iZNjkJBN8DU/5/DxrmwuHaSeiKODT72DYiQ0c9Da1JQ= github.com/grafana/grafana/pkg/util/xorm v0.0.1 h1:72QZjxWIWpSeOF8ob4aMV058kfgZyeetkAB8dmeti2o= github.com/grafana/grafana/pkg/util/xorm v0.0.1/go.mod h1:eNfbB9f2jM8o9RfwqwjY8SYm5tvowJ8Ly+iE4P9rXII= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240321101410-40158de684b2 h1:wqqaqZw+J8sLXzOjXMMde219jc6dfJPI8599UpaILZw= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240321101410-40158de684b2/go.mod h1:8Ia/R3urPmbzJ8OsdvmZvIprDwvwmYCmUbwBL+jlPOE= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b h1:HCbWyVL6vi7gxyO76gQksSPH203oBJ1MJ3JcG1OQlsg= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b/go.mod h1:01sXtHoRwI8W324IPAzuxDFOmALqYLCOhvSC2fUHWXc= github.com/grafana/pyroscope-go/godeltaprof v0.1.6 h1:nEdZ8louGAplSvIJi1HVp7kWvFvdiiYg3COLlTwJiFo= github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= github.com/grafana/pyroscope/api v0.3.0 h1:WcVKNZ8JlriJnD28wTkZray0wGo8dGkizSJXnbG7Gd8= @@ -2829,8 +2829,9 @@ github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrb github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -2840,8 +2841,9 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -2858,8 +2860,8 @@ github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJ github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= -github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= @@ -3377,8 +3379,9 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.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 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180530234432-1e491301e022/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= @@ -3844,8 +3847,9 @@ golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -4350,8 +4354,8 @@ k8s.io/kms v0.29.2/go.mod h1:s/9RC4sYRZ/6Tn6yhNjbfJuZdb8LzlXhdlBnKizeFDo= k8s.io/kube-aggregator v0.29.0 h1:N4fmtePxOZ+bwiK1RhVEztOU+gkoVkvterHgpwAuiTw= k8s.io/kube-aggregator v0.29.0/go.mod h1:bjatII63ORkFg5yUFP2qm2OC49R0wwxZhRVIyJ4Z4X0= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5 h1:QSpdNrZ9uRlV0VkqLvVO0Rqg8ioKi3oSw7O5P7pJV8M= -k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20230711102312-30195339c3c7/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/go.work b/go.work index 7ccf184922b..e5ee50b552c 100644 --- a/go.work +++ b/go.work @@ -12,3 +12,7 @@ use ( // when we release xorm we would like to release it like github.com/grafana/grafana/pkg/util/xorm // but we don't want to change all the imports. so we use replace to handle this situation replace xorm.io/xorm => ./pkg/util/xorm + +// this is required until a new version of k8s.io/component-base is released +// with an update to prometheus/common v0.48.0 +replace k8s.io/component-base => k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6 diff --git a/go.work.sum b/go.work.sum index 001aa1abaaf..84638d0ba95 100644 --- a/go.work.sum +++ b/go.work.sum @@ -611,11 +611,20 @@ github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJ github.com/grafana/grafana-plugin-sdk-go v0.215.0/go.mod h1:nBsh3jRItKQUXDF2BQkiQCPxqrsSQeb+7hiFyJTO1RE= github.com/grafana/grafana-plugin-sdk-go v0.216.0/go.mod h1:FdvSvOliqpVLnytM7e89zCFyYPDE6VOn9SIjVQRvVxM= github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b h1:HCbWyVL6vi7gxyO76gQksSPH203oBJ1MJ3JcG1OQlsg= +github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b/go.mod h1:01sXtHoRwI8W324IPAzuxDFOmALqYLCOhvSC2fUHWXc= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= +github.com/hamba/avro/v2 v2.17.2 h1:6PKpEWzJfNnvBgn7m2/8WYaDOUASxfDU+Jyb4ojDgFY= github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= +github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc= +github.com/hanwen/go-fuse/v2 v2.1.0 h1:+32ffteETaLYClUj0a3aHjZ1hOPxxaNEHiZiujuDaek= +github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= +github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= +github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= @@ -647,60 +656,186 @@ github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38= github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY= +github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w= github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw= +github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1 h1:9Xm8CKtMZIXgcopfdWk/qZ1rt0HjMgfMR9nxxSeK6vk= github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1/go.mod h1:zuHl3Hh+e9P6gmBPvcqR1HjkaWHC/csgyskg6IaFKFo= +github.com/jaegertracing/jaeger v1.41.0 h1:vVNky8dP46M2RjGaZ7qRENqylW+tBFay3h57N16Ip7M= github.com/jaegertracing/jaeger v1.41.0/go.mod h1:SIkAT75iVmA9U+mESGYuMH6UQv6V9Qy4qxo0lwfCQAc= +github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= +github.com/jedib0t/go-pretty/v6 v6.2.4 h1:wdaj2KHD2W+mz8JgJ/Q6L/T5dB7kyqEFI16eLq7GEmk= github.com/jedib0t/go-pretty/v6 v6.2.4/go.mod h1:+nE9fyyHGil+PuISTCrp7avEdo6bqoMwqZnuiK2r2a0= +github.com/jhump/gopoet v0.1.0 h1:gYjOPnzHd2nzB37xYQZxj4EIQNpBrBskRqQQ3q4ZgSg= +github.com/jhump/goprotoc v0.5.0 h1:Y1UgUX+txUznfqcGdDef8ZOVlyQvnV0pKWZH08RmZuo= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o= github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d h1:c93kUJDtVAXFEhsCh5jSxyOJmFHuzcihnslQiX8Urwo= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= +github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs= github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= +github.com/kshvakov/clickhouse v1.3.5 h1:PDTYk9VYgbjPAWry3AoDREeMgOVUFij6bh6IjlloHL0= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0otA= github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY= +github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743 h1:143Bb8f8DuGWck/xpNUOckBVYfFbBTnLevfRZ1aVVqo= +github.com/lightstep/lightstep-tracer-go v0.18.1 h1:vi1F1IQ8N7hNWytK9DpJsUfQhGuNSc19z330K6vl4zk= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lyft/protoc-gen-star v0.6.1 h1:erE0rdztuaDq3bpGifD95wfoPrSZc95nGA6tbiNYh6M= +github.com/lyft/protoc-gen-star/v2 v2.0.3 h1:/3+/2sWyXeMLzKd1bX+ixWKgEMsULrIivpDsuaF441o= +github.com/lyft/protoc-gen-validate v0.0.13 h1:KNt/RhmQTOLr7Aj8PsJ7mTronaFyx80mRTT9qF261dA= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/matryer/moq v0.3.1 h1:kLDiBJoGcusWS2BixGyTkF224aSCD8nLY24tj/NcTCs= github.com/matryer/moq v0.3.1/go.mod h1:RJ75ZZZD71hejp39j4crZLsEDszGk6iH4v4YsWFKH4s= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.52 h1:8XhG36F6oKQUDDSuz6dY3rioMzovKjW40W6ANuN0Dps= github.com/minio/minio-go/v7 v7.0.52/go.mod h1:IbbodHyjUAguneyucUaahv+VMNs/EOTV9du7A7/Z3HU= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= +github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= +github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= +github.com/mithrandie/readline-csvq v1.2.1 h1:4cfeYeVSrqKEWi/1t7CjyhFD2yS6fm+l+oe+WyoSNlI= github.com/mithrandie/readline-csvq v1.2.1/go.mod h1:ydD9Eyp3/wn8KPSNbKmMZe4RQQauCuxi26yEo4N40dk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA= +github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY/JU= +github.com/mostynb/go-grpc-compression v1.1.17 h1:N9t6taOJN3mNTTi0wDf4e3lp/G/ON1TP67Pn0vTUA9I= github.com/mostynb/go-grpc-compression v1.1.17/go.mod h1:FUSBr0QjKqQgoDG/e0yiqlR6aqyXC39+g/hFLDfSsEY= +github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8 h1:P48LjvUQpTReR3TQRbxSeSBsMXzfK0uol7eRcr7VBYQ= +github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 h1:dnMxwus89s86tI8rcGVp2HwZzlz7c5o92VOy7dSckBQ= +github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU= +github.com/nats-io/jwt/v2 v2.0.3 h1:i/O6cmIsjpcQyWDYNcq2JyZ3/VTF8SJ4JWluI5OhpvI= +github.com/nats-io/nats-server/v2 v2.5.0 h1:wsnVaaXH9VRSg+A2MVg5Q727/CqxnmPLGFQ3YZYKTQg= +github.com/nats-io/nats.go v1.12.1 h1:+0ndxwUPz3CmQ2vjbXdkC1fo3FdiOQDim4gl3Mge8Qo= +github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= +github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= +github.com/open-telemetry/opentelemetry-collector-contrib/exporter/jaegerexporter v0.74.0 h1:0dve/IbuHfQOnlIBQQwpCxIeMp7uig9DQVuvisWPDRs= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/jaegerexporter v0.74.0/go.mod h1:bIeSj+SaZdP3CE9Xae+zurdQC6DXX0tPP6NAEVmgtt4= +github.com/open-telemetry/opentelemetry-collector-contrib/exporter/kafkaexporter v0.74.0 h1:MrVOfBTNBe4n/daZjV4yvHZRR0Jg/MOCl/mNwymHwDM= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/kafkaexporter v0.74.0/go.mod h1:v4H2ATSrKfOTbQnmjCxpvuOjrO/GUURAgey9RzrPsuQ= +github.com/open-telemetry/opentelemetry-collector-contrib/exporter/zipkinexporter v0.74.0 h1:8Kk5g5PKQBUV3idjJy1NWVLLReEzjnB8C1lFgQxZ0TI= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/zipkinexporter v0.74.0/go.mod h1:UtVfxZGhPU2OvDh7H8o67VKWG9qHAHRNkhmZUWqCvME= +github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.74.0 h1:vU5ZebauzCuYNXFlQaWaYnOfjoOAnS+Sc8+oNWoHkbM= github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.74.0/go.mod h1:TEu3TnUv1TuyHtjllrUDQ/ImpyD+GrkDejZv4hxl3G8= +github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.74.0 h1:COFBWXiWnhRs9x1oYJbDg5cyiNAozp8sycriD9+1/7E= github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.74.0/go.mod h1:cAKlYKU+/8mk6ETOnD+EAi5gpXZjDrGweAB9YTYrv/g= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.74.0 h1:ww1pPXfAM0WHsymQnsN+s4B9DgwQC+GyoBq0t27JV/k= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.74.0/go.mod h1:OpEw7tyCg+iG1ywEgZ03qe5sP/8fhYdtWCMoqA8JCug= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/opencensus v0.74.0 h1:0Fh6OjlUB9HlnX90/gGiyyFvnmNBv6inj7bSaVqQ7UQ= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/opencensus v0.74.0/go.mod h1:13ekplz1UmvK99Vz2VjSBWPYqoRBEax5LPmA1tFHnhA= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/zipkin v0.74.0 h1:A5xoBaMHX1WzLfvlqK6NBXq4XIbuSVJIpec5r6PDE7U= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/zipkin v0.74.0/go.mod h1:TJT7HkhFPrJic30Vk4seF/eRk8sa0VQ442Xq/qd+DLY= +github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver v0.74.0 h1:pWNSPCKD+V4rC+MnZj8uErEbcsYUpEqU3InNYyafAPY= github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver v0.74.0/go.mod h1:0lXcDf6LUbtDxZZO3zDbRzMuL7gL1Q0FPOR8/3IBwaQ= +github.com/open-telemetry/opentelemetry-collector-contrib/receiver/kafkareceiver v0.74.0 h1:NWd9+rQTd6pELLf3copo7CEuNgKp90kgyhPozpwax2U= github.com/open-telemetry/opentelemetry-collector-contrib/receiver/kafkareceiver v0.74.0/go.mod h1:anSbwGOousKpnNAVMNP5YieA4KOFuEzHkvya0vvtsaI= +github.com/open-telemetry/opentelemetry-collector-contrib/receiver/opencensusreceiver v0.74.0 h1:Law7+BImq8DIBsdniSX8Iy2/GH5CRHpT1gsRaC9ZT8A= github.com/open-telemetry/opentelemetry-collector-contrib/receiver/opencensusreceiver v0.74.0/go.mod h1:uiW3V9EX8A5DOoxqDLuSh++ewHr+owtonCSiqMcpy3w= +github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.74.0 h1:2uysjsaqkf9STFeJN/M6i/sSYEN5pZJ94Qd2/Hg1pKE= github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.74.0/go.mod h1:qoGuayD7cAtshnKosIQHd6dobcn6/sqgUn0v/Cg2UB8= +github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e h1:4cPxUYdgaGzZIT5/j0IfqOrrXmq6bG8AwvwisMXpdrg= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= +github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU= +github.com/openzipkin/zipkin-go v0.4.1 h1:kNd/ST2yLLWhaWrkgchya40TJabe8Hioj9udfPcEO5A= github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= +github.com/pact-foundation/pact-go v1.0.4 h1:OYkFijGHoZAYbOIb1LWXrwKQbMMRUv1oQ89blD2Mh2Q= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/performancecopilot/speed v3.0.0+incompatible h1:2WnRzIquHa5QxaJKShDkLM+sc0JPuwhXzK8OYOyt3Vg= +github.com/performancecopilot/speed/v4 v4.0.0 h1:VxEDCmdkfbQYDlcr/GC9YoN9PQ6p8ulk9xVsepYy9ZY= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/phpdave11/gofpdf v1.4.2 h1:KPKiIbfwbvC/wOncwhrpRdXVj2CZTCFlw4wnoyjtHfQ= +github.com/phpdave11/gofpdi v1.0.13 h1:o61duiW8M9sMlkVXWlvP92sZJtGKENvW3VExs6dZukQ= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= +github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE= +github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM= +github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4 h1:BN/Nyn2nWMoqGRA7G7paDNDqTXE30mXGqzzybrfo05w= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245 h1:K1Xf3bKttbF+koVGaX5xngRIZ5bVjbmPnaxE/dR08uY= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/sagikazarmark/crypt v0.6.0 h1:REOEXCs/NFY/1jOCEouMuT4zEniE5YoXbvpC5X/TLF8= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4= github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= +github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e h1:uO75wNGioszjmIzcY/tvdDYKRLVvzggtAmmJkn9j4GQ= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZFQ8e24NYaBGIlnO2WGCAi67re4HHuOm0sftE/M= +github.com/segmentio/parquet-go v0.0.0-20230427215636-d483faba23a5 h1:7CWCjaHrXSUCHrRhIARMGDVKdB82tnPAQMmANeflKOw= github.com/segmentio/parquet-go v0.0.0-20230427215636-d483faba23a5/go.mod h1:+J0xQnJjm8DuQUHBO7t57EnmPbstT6+b45+p3DC9k1Q= +github.com/sercand/kuberesolver/v4 v4.0.0 h1:frL7laPDG/lFm5n98ODmWnn+cvPpzlkf3LhzuPhcHP4= github.com/sercand/kuberesolver/v4 v4.0.0/go.mod h1:F4RGyuRmMAjeXHKL+w4P7AwUnPceEAPAhxUgXZjKgvM= +github.com/sercand/kuberesolver/v5 v5.1.1 h1:CYH+d67G0sGBj7q5wLK61yzqJJ8gLLC8aeprPTHb6yY= github.com/sercand/kuberesolver/v5 v5.1.1/go.mod h1:Fs1KbKhVRnB2aDWN12NjKCB+RgYMWZJ294T3BtmVCpQ= +github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU= github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M= +github.com/shoenig/test v0.6.6 h1:Oe8TPH9wAbv++YPNDKJWUnI8Q4PPWCx3UbOfH+FxiMU= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= 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= @@ -710,13 +845,22 @@ github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdr github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/substrait-io/substrait-go v0.4.2 h1:buDnjsb3qAqTaNbOR7VKmNgXf4lYQxWEcnSGUWBtmN8= github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg= +github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= +github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= +github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o= github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= @@ -724,28 +868,61 @@ github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/valyala/fasthttp v1.6.0 h1:uWF8lgKmeaIewWVPwi4GRq2P6+R46IgYZdxWtM+GtEY= github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vinzenz/yaml v0.0.0-20170920082545-91409cdd725d h1:3wDi6J5APMqaHBVPuVd7RmHD2gRTfqbdcVSpCNoUWtk= github.com/vinzenz/yaml v0.0.0-20170920082545-91409cdd725d/go.mod h1:mb5taDqMnJiZNRQ3+02W2IFG+oEz1+dTuCXkp4jpkfo= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/weaveworks/common v0.0.0-20230511094633-334485600903 h1:ph7R2CS/0o1gBzpzK/CioUKJVsXNVXfDGR8FZ9rMZIw= github.com/weaveworks/common v0.0.0-20230511094633-334485600903/go.mod h1:rgbeLfJUtEr+G74cwFPR1k/4N0kDeaeSv/qhUNE4hm8= +github.com/weaveworks/promrus v1.2.0 h1:jOLf6pe6/vss4qGHjXmGz4oDJQA+AOCqEL3FvvZGz7M= github.com/weaveworks/promrus v1.2.0/go.mod h1:SaE82+OJ91yqjrE1rsvBWVzNZKcHYFtMUyS1+Ogs/KA= +github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/willf/bloom v2.0.3+incompatible h1:QDacWdqcAUI1MPOwIQZRy9kOR7yxfyEmxX8Wdm2/JPA= github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8= +github.com/xanzy/go-gitlab v0.15.0 h1:rWtwKTgEnXyNUGrOArN7yyc3THRkpYcKXIXia9abywQ= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= +github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xhit/go-str2duration v1.2.0 h1:BcV5u025cITWxEQKGWr1URRzrcXtu7uk8+luz3Yuhwc= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= go.einride.tech/aip v0.66.0/go.mod h1:qAhMsfT7plxBX+Oy7Huol6YUvZ0ZzdUz26yZsQwfl1M= go.opentelemetry.io/collector v0.74.0 h1:0s2DKWczGj/pLTsXGb1P+Je7dyuGx9Is4/Dri1+cS7g= go.opentelemetry.io/collector v0.74.0/go.mod h1:7NjZAvkhQ6E+NLN4EAH2hw3Nssi+F14t7mV7lMNXCto= +go.opentelemetry.io/collector/component v0.74.0 h1:W32ILPgbA5LO+m9Se61hbbtiLM6FYusNM36K5/CCOi0= go.opentelemetry.io/collector/component v0.74.0/go.mod h1:zHbWqbdmnHeIZAuO3s1Fo/kWPC2oKuolIhlPmL4bzyo= +go.opentelemetry.io/collector/confmap v0.74.0 h1:tl4fSHC/MXZiEvsZhDhd03TgzvArOe69Qn020sZsTfQ= go.opentelemetry.io/collector/confmap v0.74.0/go.mod h1:NvUhMS2v8rniLvDAnvGjYOt0qBohk6TIibb1NuyVB1Q= +go.opentelemetry.io/collector/consumer v0.74.0 h1:+kjT/ixG+4SVSHg7u9mQe0+LNDc6PuG8Wn2hoL/yGYk= go.opentelemetry.io/collector/consumer v0.74.0/go.mod h1:MuGqt8/OKVAOjrh5WHr1TR2qwHizy64ZP2uNSr+XpvI= +go.opentelemetry.io/collector/exporter v0.74.0 h1:VZxDuVz9kJM/Yten3xA/abJwLJNkxLThiao6E1ULW7c= go.opentelemetry.io/collector/exporter v0.74.0/go.mod h1:kw5YoorpKqEpZZ/a5ODSoYFK1mszzcKBNORd32S8Z7c= +go.opentelemetry.io/collector/exporter/otlpexporter v0.74.0 h1:YKvTeYcBrJwbcXNy65fJ/xytUSMurpYn/KkJD0x+DAY= go.opentelemetry.io/collector/exporter/otlpexporter v0.74.0/go.mod h1:cRbvsnpSxzySoTSnXbOGPQZu9KHlEyKkTeE21f9Q1p4= +go.opentelemetry.io/collector/featuregate v1.0.0 h1:5MGqe2v5zxaoo73BUOvUTunftX5J8RGrbFsC2Ha7N3g= +go.opentelemetry.io/collector/receiver v0.74.0 h1:jlgBFa0iByvn8VuX27UxtqiPiZE8ejmU5lb1nSptWD8= go.opentelemetry.io/collector/receiver v0.74.0/go.mod h1:SQkyATvoZCJefNkI2jnrR63SOdrmDLYCnQqXJ7ACqn0= +go.opentelemetry.io/collector/receiver/otlpreceiver v0.74.0 h1:e/X/W0z2Jtpy3Yd3CXkmEm9vSpKq/P3pKUrEVMUFBRw= go.opentelemetry.io/collector/receiver/otlpreceiver v0.74.0/go.mod h1:9X9/RYFxJIaK0JLlRZ0PpmQSSlYpY+r4KsTOj2jWj14= go.opentelemetry.io/collector/semconv v0.90.1 h1:2fkQZbefQBbIcNb9Rk1mRcWlFZgQOk7CpST1e1BK8eg= go.opentelemetry.io/contrib v0.18.0 h1:uqBh0brileIvG6luvBjdxzoFL8lxDGuhxJWsvK3BveI= @@ -758,7 +935,10 @@ go.opentelemetry.io/contrib/samplers/jaegerremote v0.16.0/go.mod h1:StxwPndBVNZD go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= go.opentelemetry.io/otel/bridge/opencensus v0.37.0 h1:ieH3gw7b1eg90ARsFAlAsX5LKVZgnCYfaDwRrK6xLHU= go.opentelemetry.io/otel/bridge/opencensus v0.37.0/go.mod h1:ddiK+1PE68l/Xk04BGTh9Y6WIcxcLrmcVxVlS0w5WZ0= +go.opentelemetry.io/otel/bridge/opentracing v1.10.0 h1:WzAVGovpC1s7KD5g4taU6BWYZP3QGSDVTlbRu9fIHw8= go.opentelemetry.io/otel/bridge/opentracing v1.10.0/go.mod h1:J7GLR/uxxqMAzZptsH0pjte3Ep4GacTCrbGBoDuHBqk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= +go.opentelemetry.io/otel/exporters/prometheus v0.37.0 h1:NQc0epfL0xItsmGgSXgfbH2C1fq2VLXkZoDFsfRNHpc= go.opentelemetry.io/otel/exporters/prometheus v0.37.0/go.mod h1:hB8qWjsStK36t50/R0V2ULFb4u95X/Q6zupXLgvjTh8= go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= @@ -776,10 +956,15 @@ go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808 h1:+Kc94D8UVEVxJnLXp/+FMfqQARZtWHfVrcRtcG8aT3g= +golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= @@ -787,6 +972,7 @@ gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPj gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4= google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= @@ -797,6 +983,7 @@ google.golang.org/genproto/googleapis/bytestream v0.0.0-20231120223509-83a465c02 google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0 h1:Y6QQt9D/syZt/Qgnz5a1y2O3WunQeeVDfS9+Xr82iFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe h1:weYsP+dNijSQVoLAb5bpUos3ciBpNU/NEVlHFKrk8pg= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa h1:wBkzraZsSqhj1M4L/nMrljUU6XasJkgHvUsq8oRGwF0= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:IN9OQUXZ0xT+26MDwZL8fJcYw+y99b0eYPA2U15Jt8o= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= @@ -806,9 +993,27 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go. google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec h1:RlWgLqCMMIYYEVcAR5MDsuHlVkaIPDAF+5Dehzg8L5A= +gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= +gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= +k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6 h1:WN8Lymy+dCTDHgn4vhUSNIB6U+0sDiv/c9Zdr0UeAnI= +k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6/go.mod h1:l0ukbPS0lwFxOzSq5ZqjutzF+5IL2TLp495PswRPSZk= +k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo= +k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8= k8s.io/kms v0.29.0/go.mod h1:mB0f9HLxRXeXUfHfn1A7rpwOlzXI1gIWu86z6buNoYA= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kube-openapi v0.0.0-20231214164306-ab13479f8bf8/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index c925c5a4d61..5712a3b683b 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -4,7 +4,7 @@ go 1.21.0 require ( k8s.io/apimachinery v0.29.2 - k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5 + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 ) require ( diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 1d3a05158ac..3ae58b12bf5 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -97,8 +97,7 @@ k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5 h1:QSpdNrZ9uRlV0VkqLvVO0Rqg8ioKi3oSw7O5P7pJV8M= -k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index b385c350b42..c7ebc754d46 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -8,14 +8,14 @@ require ( github.com/grafana/grafana-plugin-sdk-go v0.224.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f github.com/stretchr/testify v1.9.0 - golang.org/x/mod v0.14.0 + golang.org/x/mod v0.15.0 k8s.io/apimachinery v0.29.2 k8s.io/apiserver v0.29.2 k8s.io/client-go v0.29.2 k8s.io/component-base v0.29.2 k8s.io/klog v1.0.0 k8s.io/klog/v2 v2.120.1 - k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5 + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 ) require ( @@ -43,6 +43,7 @@ require ( github.com/getkin/kin-openapi v0.120.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/swag v0.22.9 // indirect @@ -89,9 +90,9 @@ require ( github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.46.0 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rivo/uniseg v0.3.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -133,7 +134,7 @@ require ( golang.org/x/term v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/tools v0.18.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 2d9c92d500a..78e4ed48566 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -76,8 +76,7 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= @@ -230,16 +229,13 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= -github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= @@ -370,8 +366,7 @@ 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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= 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= @@ -426,8 +421,7 @@ 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.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= 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= @@ -489,8 +483,7 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5 h1:QSpdNrZ9uRlV0VkqLvVO0Rqg8ioKi3oSw7O5P7pJV8M= -k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 h1:TgtAeesdhpm2SGwkQasmbeqDo8th5wOBA5h/AjTKA4I= diff --git a/pkg/build/wire/go.mod b/pkg/build/wire/go.mod index 0b2868ee974..694a3b60da9 100644 --- a/pkg/build/wire/go.mod +++ b/pkg/build/wire/go.mod @@ -6,5 +6,5 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/subcommands v1.2.0 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - golang.org/x/tools v0.17.0 + golang.org/x/tools v0.18.0 ) diff --git a/pkg/build/wire/go.sum b/pkg/build/wire/go.sum index 371f59f5482..33ae69585d9 100644 --- a/pkg/build/wire/go.sum +++ b/pkg/build/wire/go.sum @@ -8,19 +8,16 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.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 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -35,13 +32,11 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -54,6 +49,5 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 06e78c60988..c8ebd736f09 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -6,8 +6,8 @@ require ( github.com/grafana/grafana-plugin-sdk-go v0.224.0 github.com/json-iterator/go v1.1.12 github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/prometheus/client_golang v1.18.0 - github.com/prometheus/common v0.46.0 + github.com/prometheus/client_golang v1.19.0 + github.com/prometheus/common v0.48.0 github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.24.0 @@ -20,7 +20,7 @@ require ( github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect github.com/apache/arrow/go/v15 v15.0.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.50.8 // indirect + github.com/aws/aws-sdk-go v1.50.29 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect @@ -80,7 +80,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rivo/uniseg v0.3.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -103,12 +103,12 @@ require ( go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.3.0 // indirect - golang.org/x/mod v0.14.0 // indirect + golang.org/x/mod v0.15.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/oauth2 v0.19.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/tools v0.18.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect @@ -116,6 +116,6 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect ) diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 7598421b85b..a820ca92494 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -8,8 +8,7 @@ github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcy github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.50.8 h1:gY0WoOW+/Wz6XmYSgDH9ge3wnAevYDSQWPxxJvqAkP4= -github.com/aws/aws-sdk-go v1.50.8/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.29 h1:Ol2FYzesF2tsQrgVSnDWRFI60+FsSqKKdt7MLlZKubc= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -209,16 +208,13 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= -github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -320,8 +316,7 @@ 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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= 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= @@ -372,8 +367,7 @@ 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.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= 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= @@ -416,8 +410,7 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5 h1:QSpdNrZ9uRlV0VkqLvVO0Rqg8ioKi3oSw7O5P7pJV8M= -k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= From 18c4bee18ea4678665e8dd40908a031bd53f4166 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:59:04 +0200 Subject: [PATCH 104/222] DashboardLinks: Make click area bigger in the list (#86481) * DasboardLinks: Make click area bigger in the list * Update DashboardLinkList.tsx * Remove import --- .betterer.results | 3 --- .../settings/links/DashboardLinkList.tsx | 19 ++++--------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/.betterer.results b/.betterer.results index 87fef9367ea..92b2bf70fc3 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2477,9 +2477,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsEdit\`)", "0"], [0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsList\`)", "1"] ], - "public/app/features/dashboard-scene/settings/links/DashboardLinkList.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/dashboard-scene/settings/variables/components/SelectionOptionsForm.tsx:5381": [ [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], diff --git a/public/app/features/dashboard-scene/settings/links/DashboardLinkList.tsx b/public/app/features/dashboard-scene/settings/links/DashboardLinkList.tsx index d235eb7f7b7..6d3555ab6f8 100644 --- a/public/app/features/dashboard-scene/settings/links/DashboardLinkList.tsx +++ b/public/app/features/dashboard-scene/settings/links/DashboardLinkList.tsx @@ -3,18 +3,7 @@ import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { DashboardLink } from '@grafana/schema'; -import { - Button, - DeleteButton, - EmptyState, - HorizontalGroup, - Icon, - IconButton, - Stack, - TagList, - TextLink, - useStyles2, -} from '@grafana/ui'; +import { Button, DeleteButton, EmptyState, Icon, IconButton, Stack, TagList, TextLink, useStyles2 } from '@grafana/ui'; import { Trans, t } from 'app/core/internationalization'; interface DashboardLinkListProps { @@ -80,12 +69,12 @@ export function DashboardLinkList({
- ); diff --git a/public/app/features/serviceaccounts/ServiceAccountPage.tsx b/public/app/features/serviceaccounts/ServiceAccountPage.tsx index 86be0ebee93..f99bc03cb4a 100644 --- a/public/app/features/serviceaccounts/ServiceAccountPage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountPage.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { getTimeZone, NavModelItem } from '@grafana/data'; -import { Button, ConfirmModal, HorizontalGroup, IconButton } from '@grafana/ui'; +import { Button, ConfirmModal, IconButton, Stack } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; @@ -137,7 +137,7 @@ export const ServiceAccountPageUnconnected = ({
{serviceAccount && !serviceAccount.isExternal && ( - + )} - + {tokens && ( Date: Fri, 26 Apr 2024 11:35:38 -0400 Subject: [PATCH 137/222] datatrails: fix bookmark/recent trail detection, prevent duplications, save trail on browser close/reload (#85677) * fix: persistence trail detection, save on unload - fixes detection on bookmarks and recents when current step isn't final - now save current trail on browser close or reload (unload) - refresh page or return to URL that corresponds to a recent trail will resume that trail instead of creating a duplicate recent trail - do not create a recent trail out of an empty starting trail * fix: bookmarks status after making new step - clone bookmark trail state to prevent it from being changed by user - re-evaluate bookmark status after creating new step --- public/app/features/trails/DataTrail.tsx | 7 +- public/app/features/trails/DataTrailsApp.tsx | 38 ++- .../app/features/trails/DataTrailsHistory.tsx | 4 + .../trails/TrailStore/TrailStore.test.ts | 284 ++++++++++++++++++ .../features/trails/TrailStore/TrailStore.ts | 68 +++-- .../trails/TrailStore/useBookmarkState.ts | 17 +- 6 files changed, 389 insertions(+), 29 deletions(-) diff --git a/public/app/features/trails/DataTrail.tsx b/public/app/features/trails/DataTrail.tsx index e2ac91a180e..4b3820f5f96 100644 --- a/public/app/features/trails/DataTrail.tsx +++ b/public/app/features/trails/DataTrail.tsx @@ -95,12 +95,17 @@ export class DataTrail extends SceneObjectBase { this.enableUrlSync(); + // Save the current trail as a recent if the browser closes or reloads + const saveRecentTrail = () => getTrailStore().setRecentTrail(this); + window.addEventListener('unload', saveRecentTrail); + return () => { this.disableUrlSync(); if (!this.state.embedded) { - getTrailStore().setRecentTrail(this); + saveRecentTrail(); } + window.removeEventListener('unload', saveRecentTrail); }; } diff --git a/public/app/features/trails/DataTrailsApp.tsx b/public/app/features/trails/DataTrailsApp.tsx index 9622851ca5a..9f2511306be 100644 --- a/public/app/features/trails/DataTrailsApp.tsx +++ b/public/app/features/trails/DataTrailsApp.tsx @@ -75,14 +75,6 @@ function DataTrailView({ trail }: { trail: DataTrail }) { useEffect(() => { if (!isInitialized) { - // Set the initial state based on the URL. - getUrlSyncManager().initSync(trail); - // Any further changes to the state should occur directly to the state, not through the URL. - // We want to stop automatically syncing the URL state (and vice versa) to the trail after this point. - // Moving forward in the lifecycle of the trail, we will make explicit calls to trail.syncTrailToUrl() - // so we can ensure the URL is kept up to date at key points. - getUrlSyncManager().cleanUp(trail); - getTrailStore().setRecentTrail(trail); setIsInitialized(true); } @@ -100,7 +92,7 @@ let dataTrailsApp: DataTrailsApp; export function getDataTrailsApp() { if (!dataTrailsApp) { dataTrailsApp = new DataTrailsApp({ - trail: newMetricsTrail(), + trail: getInitialTrail(), home: new DataTrailsHome({}), }); } @@ -108,6 +100,34 @@ export function getDataTrailsApp() { return dataTrailsApp; } +/** + * Get the initial trail for the app to work with based on the current URL + * + * It will either be a new trail that will be started based on the state represented + * in the URL parameters, or it will be the most recently used trail (according to the trail store) + * which has its current history step matching the URL parameters. + * + * The reason for trying to reinitialize from the recent trail is to resolve an issue + * where refreshing the browser would wipe the step history. This allows you to preserve + * it between browser refreshes, or when reaccessing the same URL. + */ +function getInitialTrail() { + const newTrail = newMetricsTrail(); + + // Set the initial state of the newTrail based on the URL, + // In case we are initializing from an externally created URL or a page reload + getUrlSyncManager().initSync(newTrail); + // Remove the URL sync for now. It will be restored on the trail if it is activated. + getUrlSyncManager().cleanUp(newTrail); + + // If one of the recent trails is a match to the newTrail derived from the current URL, + // let's restore that trail so that a page refresh doesn't create a new trail. + const recentMatchingTrail = getTrailStore().findMatchingRecentTrail(newTrail)?.resolve(); + + // If there is a matching trail, initialize with that. Otherwise, use the new trail. + return recentMatchingTrail || newTrail; +} + function getStyles(theme: GrafanaTheme2) { return { customPage: css({ diff --git a/public/app/features/trails/DataTrailsHistory.tsx b/public/app/features/trails/DataTrailsHistory.tsx index 5c08c590e4e..539174d277f 100644 --- a/public/app/features/trails/DataTrailsHistory.tsx +++ b/public/app/features/trails/DataTrailsHistory.tsx @@ -23,6 +23,10 @@ export interface DataTrailsHistoryState extends SceneObjectState { steps: DataTrailHistoryStep[]; } +export function isDataTrailsHistoryState(state: SceneObjectState): state is DataTrailsHistoryState { + return 'currentStep' in state && 'steps' in state; +} + export interface DataTrailHistoryStep { description: string; type: TrailStepType; diff --git a/public/app/features/trails/TrailStore/TrailStore.test.ts b/public/app/features/trails/TrailStore/TrailStore.test.ts index 1b6c1447fa6..8fe31ff9181 100644 --- a/public/app/features/trails/TrailStore/TrailStore.test.ts +++ b/public/app/features/trails/TrailStore/TrailStore.test.ts @@ -165,7 +165,184 @@ describe('TrailStore', () => { // There should now be two trails expect(store.recent.length).toBe(2); }); + + test('deserializeTrail must show state of current step when not last step', () => { + const trailSerialized: SerializedTrail = { + history: [ + history[0], + history[1], + { + ...history[1], + urlValues: { + ...history[1].urlValues, + metric: 'something_else', + }, + parentIndex: 1, + }, + ], + currentStep: 1, + }; + + // @ts-ignore #2341 -- deliberately access private method to construct trail object for testing purposes + const trail = getTrailStore()._deserializeTrail(trailSerialized); + + // + expect(trail.state.metric).not.toEqual('something_else'); + expect(trail.state.metric).toEqual(history[1].urlValues.metric); + }); }); + + describe('Initialize store with one recent trail with non final current step', () => { + const history: SerializedTrail['history'] = [ + { + urlValues: { + from: 'now-1h', + to: 'now', + 'var-ds': 'ds', + 'var-filters': [], + refresh: '', + }, + type: 'start', + description: 'Test', + parentIndex: -1, + }, + { + urlValues: { + metric: 'current_metric', + from: 'now-1h', + to: 'now', + 'var-ds': 'ds', + 'var-filters': [], + refresh: '', + }, + type: 'metric', + description: 'Test', + parentIndex: 0, + }, + { + urlValues: { + metric: 'final_metric', + from: 'now-1h', + to: 'now', + 'var-ds': 'ds', + 'var-filters': [], + refresh: '', + }, + type: 'metric', + description: 'Test', + parentIndex: 1, + }, + ]; + + beforeEach(() => { + localStorage.clear(); + localStorage.setItem(RECENT_TRAILS_KEY, JSON.stringify([{ history, currentStep: 1 }])); + getTrailStore().load(); + }); + + it('should accurately load recent trails', () => { + const store = getTrailStore(); + expect(store.recent.length).toBe(1); + const trail = store.recent[0].resolve(); + expect(trail.state.history.state.steps.length).toBe(3); + expect(trail.state.history.state.steps[0].type).toBe('start'); + expect(trail.state.history.state.steps[1].type).toBe('metric'); + expect(trail.state.history.state.steps[1].trailState.metric).toBe('current_metric'); + expect(trail.state.history.state.steps[2].type).toBe('metric'); + expect(trail.state.history.state.steps[2].trailState.metric).toBe('final_metric'); + expect(trail.state.history.state.currentStep).toBe(1); + }); + + it('should have no bookmarked trails', () => { + const store = getTrailStore(); + expect(store.bookmarks.length).toBe(0); + }); + + describe('Add a new recent trail with equivalent current step state', () => { + const store = getTrailStore(); + + const duplicateTrailSerialized: SerializedTrail = { + history: [ + history[0], + history[1], + history[2], + { + ...history[2], + urlValues: { + ...history[1].urlValues, + metric: 'different_metric_in_the_middle', + }, + }, + { + ...history[1], + }, + ], + currentStep: 4, + }; + + beforeEach(() => { + // We expect the initialized trail to be there + expect(store.recent.length).toBe(1); + expect(store.recent[0].resolve().state.history.state.steps.length).toBe(3); + + // @ts-ignore #2341 -- deliberately access private method to construct trail object for testing purposes + const duplicateTrail = store._deserializeTrail(duplicateTrailSerialized); + store.setRecentTrail(duplicateTrail); + }); + + it('should still be only one recent trail', () => { + expect(store.recent.length).toBe(1); + }); + + it('it should only contain the new trail', () => { + const newRecentTrail = store.recent[0].resolve(); + expect(newRecentTrail.state.history.state.steps.length).toBe(duplicateTrailSerialized.history.length); + + // @ts-ignore #2341 -- deliberately access private method to construct trail object for testing purposes + const newRecent = store._serializeTrail(newRecentTrail); + expect(newRecent.currentStep).toBe(duplicateTrailSerialized.currentStep); + expect(newRecent.history.length).toBe(duplicateTrailSerialized.history.length); + }); + }); + + it.each([ + ['metric', 'different_metric'], + ['from', 'now-1y'], + ['to', 'now-30m'], + ['var-ds', '1234'], + ['var-groupby', 'job'], + ['var-filters', 'cluster|=|dev-eu-west-2'], + ])(`new recent trails with a different '%p' value should insert new entry`, (key, differentValue) => { + const store = getTrailStore(); + // We expect the initialized trail to be there + expect(store.recent.length).toBe(1); + + const differentTrailSerialized: SerializedTrail = { + history: [ + history[0], + history[1], + history[2], + { + ...history[2], + urlValues: { + ...history[1].urlValues, + [key]: differentValue, + }, + parentIndex: 1, + }, + ], + currentStep: 3, + }; + + // @ts-ignore #2341 -- deliberately access private method to construct trail object for testing purposes + const differentTrail = store._deserializeTrail(differentTrailSerialized); + store.setRecentTrail(differentTrail); + + // There should now be two trails + expect(store.recent.length).toBe(2); + }); + }); + describe('Initialize store with one bookmark trail', () => { beforeEach(() => { localStorage.clear(); @@ -264,4 +441,111 @@ describe('TrailStore', () => { expect(localStorage.getItem(BOOKMARKED_TRAILS_KEY)).toBe('[]'); }); }); + + describe('Initialize store with one bookmark trail not on final step', () => { + beforeEach(() => { + localStorage.clear(); + localStorage.setItem( + BOOKMARKED_TRAILS_KEY, + JSON.stringify([ + { + history: [ + { + urlValues: { + from: 'now-1h', + to: 'now', + 'var-ds': 'prom-mock', + 'var-filters': [], + refresh: '', + }, + type: 'start', + }, + { + urlValues: { + metric: 'bookmarked_metric', + from: 'now-1h', + to: 'now', + 'var-ds': 'prom-mock', + 'var-filters': [], + refresh: '', + }, + type: 'time', + }, + { + urlValues: { + metric: 'some_other_metric', + from: 'now-1h', + to: 'now', + 'var-ds': 'prom-mock', + 'var-filters': [], + refresh: '', + }, + type: 'metric', + }, + ], + currentStep: 1, + }, + ]) + ); + getTrailStore().load(); + }); + + const store = getTrailStore(); + + it('should have no recent trails', () => { + expect(store.recent.length).toBe(0); + }); + + it('should accurately load bookmarked trails', () => { + expect(store.bookmarks.length).toBe(1); + const trail = store.bookmarks[0].resolve(); + expect(trail.state.history.state.steps.length).toBe(3); + expect(trail.state.history.state.steps[0].type).toBe('start'); + expect(trail.state.history.state.steps[1].type).toBe('time'); + expect(trail.state.history.state.steps[2].type).toBe('metric'); + }); + + it('should save a new recent trail based on the bookmark', () => { + expect(store.recent.length).toBe(0); + const trail = store.bookmarks[0].resolve(); + store.setRecentTrail(trail); + expect(store.recent.length).toBe(1); + }); + + it('should be able to obtain index of bookmark', () => { + const trail = store.bookmarks[0].resolve(); + const index = store.getBookmarkIndex(trail); + expect(index).toBe(0); + }); + + it('index should be undefined for removed bookmarks', () => { + const trail = store.bookmarks[0].resolve(); + store.removeBookmark(0); + const index = store.getBookmarkIndex(trail); + expect(index).toBe(undefined); + }); + + it('index should be undefined for a trail that has changed since it was bookmarked', () => { + const trail = store.bookmarks[0].resolve(); + trail.setState({ metric: 'something_completely_different' }); + const index = store.getBookmarkIndex(trail); + expect(index).toBe(undefined); + }); + + it('should be able to obtain index of a bookmark for a trail that changed back to bookmarked state', () => { + const trail = store.bookmarks[0].resolve(); + trail.setState({ metric: 'something_completely_different' }); + expect(store.getBookmarkIndex(trail)).toBe(undefined); + trail.setState({ metric: 'bookmarked_metric' }); + expect(store.getBookmarkIndex(trail)).toBe(0); + }); + + it('should remove a bookmark', () => { + expect(store.bookmarks.length).toBe(1); + store.removeBookmark(0); + expect(store.bookmarks.length).toBe(0); + jest.advanceTimersByTime(2000); + expect(localStorage.getItem(BOOKMARKED_TRAILS_KEY)).toBe('[]'); + }); + }); }); diff --git a/public/app/features/trails/TrailStore/TrailStore.ts b/public/app/features/trails/TrailStore/TrailStore.ts index 082a2a80099..02c7cd92446 100644 --- a/public/app/features/trails/TrailStore/TrailStore.ts +++ b/public/app/features/trails/TrailStore/TrailStore.ts @@ -26,12 +26,12 @@ export interface SerializedTrail { export class TrailStore { private _recent: Array> = []; private _bookmarks: Array> = []; - private _save; + private _save: () => void; constructor() { this.load(); - this._save = debounce(() => { + const doSave = () => { const serializedRecent = this._recent .slice(0, MAX_RECENT_TRAILS) .map((trail) => this._serializeTrail(trail.resolve())); @@ -39,7 +39,15 @@ export class TrailStore { const serializedBookmarks = this._bookmarks.map((trail) => this._serializeTrail(trail.resolve())); localStorage.setItem(BOOKMARKED_TRAILS_KEY, JSON.stringify(serializedBookmarks)); - }, 1000); + }; + + this._save = debounce(doSave, 1000); + + window.addEventListener('beforeunload', (ev) => { + // Before closing or reloading the page, we want to remove the debounce from `_save` so that + // any calls to is on event `unload` are actualized. Debouncing would cause a delay until after the page has been unloaded. + this._save = doSave; + }); } private _loadFromStorage(key: string) { @@ -70,6 +78,8 @@ export class TrailStore { const currentStep = t.currentStep ?? trail.state.history.state.steps.length - 1; trail.state.history.setState({ currentStep }); + // The state change listeners aren't activated yet, so maually change to the current step state + trail.setState(trail.state.history.state.steps[currentStep].trailState); return trail; } @@ -107,29 +117,45 @@ export class TrailStore { this._refreshBookmarkIndexMap(); } - setRecentTrail(trail: DataTrail) { - this._recent = this._recent.filter((t) => t !== trail.getRef()); + setRecentTrail(recentTrail: DataTrail) { + const { steps } = recentTrail.state.history.state; + if (steps.length === 0 || (steps.length === 1 && steps[0].type === 'start')) { + // We do not set an uninitialized trail, or a single node "start" trail as recent + return; + } - // Check if any existing "recent" entries have equivalent 'current' urlValue to the new trail - const newTrailUrlValues = getCurrentUrlValues(this._serializeTrail(trail)) || {}; + // Remove the `recentTrail` from the list if it already exists there + this._recent = this._recent.filter((t) => t !== recentTrail.getRef()); + + // Check if any existing "recent" entries have equivalent urlState to the new recentTrail + const recentUrlState = getUrlStateForComparison(recentTrail); // this._recent = this._recent.filter((t) => { // Use the current step urlValues to filter out equivalent states - const urlValues = getCurrentUrlValues(this._serializeTrail(t.resolve())); + const urlState = getUrlStateForComparison(t.resolve()); // Only keep trails with sufficiently unique urlValues on their current step - return !isEqual(newTrailUrlValues, urlValues); + return !isEqual(recentUrlState, urlState); }); - this._recent.unshift(trail.getRef()); + this._recent.unshift(recentTrail.getRef()); this._save(); } + findMatchingRecentTrail(trail: DataTrail) { + const matchUrlState = getUrlStateForComparison(trail); + return this._recent.find((t) => { + const urlState = getUrlStateForComparison(t.resolve()); + return isEqual(matchUrlState, urlState); + }); + } + // Bookmarked Trails get bookmarks() { return this._bookmarks; } addBookmark(trail: DataTrail) { - this._bookmarks.unshift(trail.getRef()); + const bookmark = new DataTrail(sceneUtils.cloneSceneObjectState(trail.state)); + this._bookmarks.unshift(bookmark.getRef()); this._refreshBookmarkIndexMap(); this._save(); dispatch(notifyApp(createBookmarkSavedNotification())); @@ -155,6 +181,7 @@ export class TrailStore { this._bookmarkIndexMap.clear(); this._bookmarks.forEach((bookmarked, index) => { const trail = bookmarked.resolve(); + const key = getBookmarkKey(trail); // If there are duplicate bookmarks, the latest index will be kept this._bookmarkIndexMap.set(key, index); @@ -162,15 +189,24 @@ export class TrailStore { } } -function getBookmarkKey(trail: DataTrail) { +function getUrlStateForComparison(trail: DataTrail) { const urlState = getUrlSyncManager().getUrlState(trail); - // Not part of state + // Make a few corrections + + // Omit some URL parameters that are not useful for state comparison delete urlState.actionView; + delete urlState.layout; + // Populate defaults if (urlState['var-groupby'] === '') { urlState['var-groupby'] = '$__all'; } - const key = JSON.stringify(urlState); + + return urlState; +} + +function getBookmarkKey(trail: DataTrail) { + const key = JSON.stringify(getUrlStateForComparison(trail)); return key; } @@ -182,7 +218,3 @@ export function getTrailStore(): TrailStore { return store; } - -function getCurrentUrlValues({ history, currentStep }: SerializedTrail) { - return history[currentStep]?.urlValues || history.at(-1)?.urlValues; -} diff --git a/public/app/features/trails/TrailStore/useBookmarkState.ts b/public/app/features/trails/TrailStore/useBookmarkState.ts index 632d26e0695..516c73018e3 100644 --- a/public/app/features/trails/TrailStore/useBookmarkState.ts +++ b/public/app/features/trails/TrailStore/useBookmarkState.ts @@ -1,6 +1,9 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; + +import { SceneObjectStateChangedEvent } from '@grafana/scenes'; import { DataTrail } from '../DataTrail'; +import { isDataTrailsHistoryState } from '../DataTrailsHistory'; import { reportExploreMetrics } from '../interactions'; import { getTrailStore } from './TrailStore'; @@ -14,6 +17,18 @@ export function useBookmarkState(trail: DataTrail) { const [bookmarkIndex, setBookmarkIndex] = useState(indexOnRender); + useEffect(() => { + const sub = trail.subscribeToEvent(SceneObjectStateChangedEvent, ({ payload: { prevState, newState } }) => { + if (isDataTrailsHistoryState(prevState) && isDataTrailsHistoryState(newState)) { + if (newState.steps.length > prevState.steps.length) { + // When we add new steps, we need to re-evaluate whether or not it is still a bookmark + setBookmarkIndex(getTrailStore().getBookmarkIndex(trail)); + } + } + }); + return () => sub.unsubscribe(); + }, [trail]); + // Check if index changed and force a re-render if (indexOnRender !== bookmarkIndex) { setBookmarkIndex(indexOnRender); From f2ca11591357c8d504f22bbb6e06b425e27fc980 Mon Sep 17 00:00:00 2001 From: Ben Sully Date: Fri, 26 Apr 2024 16:44:36 +0100 Subject: [PATCH 138/222] Scenes: support interpolations in TemplateSrv.replace (#86990) TemplateSrv.replace takes an optional 'interpolations' argument which records information about the variables which were found during the interpolation. Until [this Scenes PR][scenes PR] this wasn't supported by Scenes so the interpolations argument was being ignored if dashboard scenes were enabled. This commit bumps the scenes version and passes the interpolations array along to the scenes function. [scenes PR]: https://github.com/grafana/scenes/pull/708 --- package.json | 2 +- public/app/features/templating/template_srv.ts | 6 ++++-- yarn.lock | 10 +++++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index ded110dd49a..af03ac65285 100644 --- a/package.json +++ b/package.json @@ -252,7 +252,7 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "^4.12.0", + "@grafana/scenes": "^4.13.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index 9aa3133080c..033bf8b4d79 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -253,7 +253,8 @@ export class TemplateSrv implements BaseTemplateSrv { scopedVars.__sceneObject.value, target, scopedVars, - format as string | VariableCustomFormatterFn | undefined + format as string | VariableCustomFormatterFn | undefined, + interpolations ); } @@ -263,7 +264,8 @@ export class TemplateSrv implements BaseTemplateSrv { window.__grafanaSceneContext, target, scopedVars, - format as string | VariableCustomFormatterFn | undefined + format as string | VariableCustomFormatterFn | undefined, + interpolations ); } diff --git a/yarn.lock b/yarn.lock index e541abbec12..aaba0617029 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3796,9 +3796,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:^4.12.0": - version: 4.12.0 - resolution: "@grafana/scenes@npm:4.12.0" +"@grafana/scenes@npm:^4.13.0": + version: 4.13.0 + resolution: "@grafana/scenes@npm:4.13.0" dependencies: "@grafana/e2e-selectors": "npm:10.3.3" react-grid-layout: "npm:1.3.4" @@ -3812,7 +3812,7 @@ __metadata: "@grafana/ui": ^10.0.3 react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/d59564176f432e947d88e1c25dc901dd424aa61b4a0fa91b5f30704ec1da698b0c67b3c0a79caf112df83a0b84b583a8732d157a7eec87cd43cdbb4949007d3f + checksum: 10/2785516164ff557b325b7425861f2aee895656ae635d9e9e6b9c882229f75ea19fa0f9402d4e827efcc46103918aa130f236ec850913e56dadc06f1ffeefb1f9 languageName: node linkType: hard @@ -17879,7 +17879,7 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:^4.12.0" + "@grafana/scenes": "npm:^4.13.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^1.3.0-rc1" From 1dbb3bfdea76da4b6d6604288c91b547c9e0baa1 Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:44:18 +0100 Subject: [PATCH 139/222] Scenes/PanelVizTypePicker: Use default tab if listMode is unsupported (#86885) Closes #84565 --- .../panel-edit/PanelVizTypePicker.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx index 7a8cf670d9e..4338c159725 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useLocalStorage } from 'react-use'; import { GrafanaTheme2, PanelData, SelectableValue } from '@grafana/data'; @@ -30,7 +30,21 @@ export function PanelVizTypePicker({ vizManager, data, onChange }: Props) { const defaultTab = isWidgetEnabled ? VisualizationSelectPaneTab.Widgets : VisualizationSelectPaneTab.Visualizations; const panelModel = useMemo(() => new PanelModelCompatibilityWrapper(panel), [panel]); + const supportedListModes = useMemo( + () => + new Set([ + VisualizationSelectPaneTab.Widgets, + VisualizationSelectPaneTab.Visualizations, + VisualizationSelectPaneTab.Suggestions, + ]), + [] + ); const [listMode, setListMode] = useLocalStorage(tabKey, defaultTab); + useEffect(() => { + if (listMode && !supportedListModes.has(listMode)) { + setListMode(defaultTab); + } + }, [defaultTab, listMode, setListMode, supportedListModes]); const radioOptions: Array> = [ { label: 'Visualizations', value: VisualizationSelectPaneTab.Visualizations }, From d5fde99c6d46ee17725aa01213f2b779a5728df8 Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:32:01 -0400 Subject: [PATCH 140/222] Docs: Add value mappings shared content (#86996) * Added value mappings shared file * Fixed name of file * Fixed list of visualizations using shared file * Added shared file to visualizations * Updated shared file and added to canvas * Updated shared file intro text * Removed future tense --- .../visualizations/bar-chart/index.md | 4 ++++ .../visualizations/bar-gauge/index.md | 4 ++++ .../visualizations/candlestick/index.md | 4 ++++ .../visualizations/canvas/index.md | 4 ++++ .../visualizations/gauge/index.md | 6 +++++- .../visualizations/geomap/index.md | 4 ++++ .../visualizations/histogram/index.md | 4 ++++ .../visualizations/pie-chart/index.md | 4 ++++ .../visualizations/stat/index.md | 4 ++++ .../visualizations/state-timeline/index.md | 4 ++++ .../visualizations/status-history/index.md | 4 ++++ .../visualizations/table/index.md | 4 ++++ .../visualizations/time-series/index.md | 4 ++++ .../visualizations/trend/index.md | 4 ++++ .../visualizations/value-mappings-options.md | 20 +++++++++++++++++++ 15 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 docs/sources/shared/visualizations/value-mappings-options.md diff --git a/docs/sources/panels-visualizations/visualizations/bar-chart/index.md b/docs/sources/panels-visualizations/visualizations/bar-chart/index.md index 41536af170b..ed87d1427ea 100644 --- a/docs/sources/panels-visualizations/visualizations/bar-chart/index.md +++ b/docs/sources/panels-visualizations/visualizations/bar-chart/index.md @@ -199,6 +199,10 @@ You can set standard min/max options to define hard limits of the Y-axis. For mo {{< docs/shared lookup="visualizations/multiple-y-axes.md" source="grafana" version="" leveloffset="+2" >}} +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [Add a field override]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-overrides#add-a-field-override" [Add a field override]: "/docs/grafana-cloud/ -> /docs/grafana//panels-visualizations/configure-overrides#add-a-field-override" diff --git a/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md b/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md index 263d56c3c11..41198f0bb72 100644 --- a/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md +++ b/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md @@ -130,6 +130,10 @@ Automatically show y-axis scrollbar when there's a large amount of data. This option only applies when bar size is set to manual. {{% /admonition %}} +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [Calculation types]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/query-transform-data/calculation-types" [Calculation types]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/calculation-types" diff --git a/docs/sources/panels-visualizations/visualizations/candlestick/index.md b/docs/sources/panels-visualizations/visualizations/candlestick/index.md index 715f20a246c..099222aa64b 100644 --- a/docs/sources/panels-visualizations/visualizations/candlestick/index.md +++ b/docs/sources/panels-visualizations/visualizations/candlestick/index.md @@ -118,6 +118,10 @@ The candlestick visualization is based on the time series visualization. It can {{< docs/shared lookup="visualizations/tooltip-options-2.md" source="grafana" version="" >}} +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [time series visualization]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/visualizations/time-series" [time series visualization]: "/docs/grafana-cloud/ -> /docs/grafana//panels-visualizations/visualizations/time-series" diff --git a/docs/sources/panels-visualizations/visualizations/canvas/index.md b/docs/sources/panels-visualizations/visualizations/canvas/index.md index d853ab32a55..a378485ec5d 100644 --- a/docs/sources/panels-visualizations/visualizations/canvas/index.md +++ b/docs/sources/panels-visualizations/visualizations/canvas/index.md @@ -172,3 +172,7 @@ If multiple elements use the same field name, and you want to control which elem 1. Reference the new unique field alias to create the element and field override. {{< video-embed src="/media/docs/grafana/canvas-data-links-9-4-0.mp4" max-width="750px" caption="Data links demo" >}} + +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} diff --git a/docs/sources/panels-visualizations/visualizations/gauge/index.md b/docs/sources/panels-visualizations/visualizations/gauge/index.md index aa2b3224058..047f33eaccc 100644 --- a/docs/sources/panels-visualizations/visualizations/gauge/index.md +++ b/docs/sources/panels-visualizations/visualizations/gauge/index.md @@ -105,7 +105,11 @@ Adjust the sizes of the gauge text. - **Title -** Enter a numeric value for the gauge title size. - **Value -** Enter a numeric value for the gauge value size. +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} -[Calculation types]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/query-transform-data/calculation-types" +[Calculation types]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/query-transform-data/calculation-types" [Calculation types]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/calculation-types" {{% /docs/reference %}} diff --git a/docs/sources/panels-visualizations/visualizations/geomap/index.md b/docs/sources/panels-visualizations/visualizations/geomap/index.md index d3d3cc12e62..ff3df90ac7f 100644 --- a/docs/sources/panels-visualizations/visualizations/geomap/index.md +++ b/docs/sources/panels-visualizations/visualizations/geomap/index.md @@ -603,6 +603,10 @@ Displays debug information in the upper right corner. This can be useful for deb - **None** displays tooltips only when a data point is clicked. - **Details** displays tooltips when a mouse pointer hovers over a data point. +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [provisioning docs page]: "/docs/grafana/ -> /docs/grafana//administration/provisioning" [provisioning docs page]: "/docs/grafana-cloud/ -> /docs/grafana//administration/provisioning" diff --git a/docs/sources/panels-visualizations/visualizations/histogram/index.md b/docs/sources/panels-visualizations/visualizations/histogram/index.md index 1c2583154d8..37dc3c95e28 100644 --- a/docs/sources/panels-visualizations/visualizations/histogram/index.md +++ b/docs/sources/panels-visualizations/visualizations/histogram/index.md @@ -127,6 +127,10 @@ Gradient color is generated based on the hue of the line color. Choose a [standard calculations][] to show in the legend. You can select more than one. +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [color scheme]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-standard-options#color-scheme" [color scheme]: "/docs/grafana-cloud/ -> /docs/grafana//panels-visualizations/configure-standard-options#color-scheme" diff --git a/docs/sources/panels-visualizations/visualizations/pie-chart/index.md b/docs/sources/panels-visualizations/visualizations/pie-chart/index.md index 20a6c3d63ac..c10605930f3 100644 --- a/docs/sources/panels-visualizations/visualizations/pie-chart/index.md +++ b/docs/sources/panels-visualizations/visualizations/pie-chart/index.md @@ -113,6 +113,10 @@ Select values to display in the legend. You can select more than one. - **Percent:** The percentage of the whole. - **Value:** The raw numerical value. +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [Calculation types]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/query-transform-data/calculation-types" [Calculation types]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/calculation-types" diff --git a/docs/sources/panels-visualizations/visualizations/stat/index.md b/docs/sources/panels-visualizations/visualizations/stat/index.md index 87e74088d0b..8a7bc8632e4 100644 --- a/docs/sources/panels-visualizations/visualizations/stat/index.md +++ b/docs/sources/panels-visualizations/visualizations/stat/index.md @@ -189,6 +189,10 @@ Adjust the sizes of the gauge text. - **Title -** Enter a numeric value for the gauge title size. - **Value -** Enter a numeric value for the gauge value size. +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [Calculation types]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/query-transform-data/calculation-types" [Calculation types]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/calculation-types" diff --git a/docs/sources/panels-visualizations/visualizations/state-timeline/index.md b/docs/sources/panels-visualizations/visualizations/state-timeline/index.md index 6f47fa94a2f..3bf01a2a768 100644 --- a/docs/sources/panels-visualizations/visualizations/state-timeline/index.md +++ b/docs/sources/panels-visualizations/visualizations/state-timeline/index.md @@ -140,6 +140,10 @@ When the legend option is enabled it can show either the value mappings or the t {{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="" >}} +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [Color scheme]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-standard-options#color-scheme" [Color scheme]: "/docs/grafana-cloud/ -> /docs/grafana//panels-visualizations/configure-standard-options#color-scheme" diff --git a/docs/sources/panels-visualizations/visualizations/status-history/index.md b/docs/sources/panels-visualizations/visualizations/status-history/index.md index 12ad71fd1c3..c478ee225f7 100644 --- a/docs/sources/panels-visualizations/visualizations/status-history/index.md +++ b/docs/sources/panels-visualizations/visualizations/status-history/index.md @@ -120,6 +120,10 @@ When the legend option is enabled it can show either the value mappings or the t {{< docs/shared lookup="visualizations/tooltip-options-1.md" source="grafana" version="" >}} +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [Value mappings]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-value-mappings" [Value mappings]: "/docs/grafana-cloud/ -> /docs/grafana//panels-visualizations/configure-value-mappings" diff --git a/docs/sources/panels-visualizations/visualizations/table/index.md b/docs/sources/panels-visualizations/visualizations/table/index.md index a23ce3c233d..2c3a93b2f76 100644 --- a/docs/sources/panels-visualizations/visualizations/table/index.md +++ b/docs/sources/panels-visualizations/visualizations/table/index.md @@ -234,6 +234,10 @@ The system applies the calculation to all numeric fields if you do not select a If you want to show the number of rows in the dataset instead of the number of values in the selected fields, select the **Count** calculation and enable **Count rows**. +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [calculations]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/query-transform-data/calculation-types" [calculations]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/calculation-types" diff --git a/docs/sources/panels-visualizations/visualizations/time-series/index.md b/docs/sources/panels-visualizations/visualizations/time-series/index.md index 67d81494027..91eb649aa1c 100644 --- a/docs/sources/panels-visualizations/visualizations/time-series/index.md +++ b/docs/sources/panels-visualizations/visualizations/time-series/index.md @@ -303,6 +303,10 @@ The following image shows a bar chart with the **Green-Yellow-Red (by value)** c {{< figure src="/static/img/docs/time-series-panel/gradient_mode_scheme_bars.png" max-width="1200px" caption="Color scheme: Green-Yellow-Red" >}} +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [Color scheme]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/configure-standard-options#color-scheme" [Color scheme]: "/docs/grafana-cloud/ -> /docs/grafana//panels-visualizations/configure-standard-options#color-scheme" diff --git a/docs/sources/panels-visualizations/visualizations/trend/index.md b/docs/sources/panels-visualizations/visualizations/trend/index.md index 823c20745a6..1fa72c8d947 100644 --- a/docs/sources/panels-visualizations/visualizations/trend/index.md +++ b/docs/sources/panels-visualizations/visualizations/trend/index.md @@ -42,6 +42,10 @@ For example, you could represent engine power and torque versus speed where spee {{< docs/shared lookup="visualizations/tooltip-options-2.md" source="grafana" version="" >}} +## Value mappings + +{{< docs/shared lookup="visualizations/value-mappings-options.md" source="grafana" version="" >}} + {{% docs/reference %}} [Time series visualization]: "/docs/grafana/ -> /docs/grafana//panels-visualizations/visualizations/time-series" [Time series visualization]: "/docs/grafana-cloud/ -> /docs/grafana//panels-visualizations/visualizations/time-series" diff --git a/docs/sources/shared/visualizations/value-mappings-options.md b/docs/sources/shared/visualizations/value-mappings-options.md new file mode 100644 index 00000000000..937801addec --- /dev/null +++ b/docs/sources/shared/visualizations/value-mappings-options.md @@ -0,0 +1,20 @@ +--- +title: Value mappings options +comments: | + This file is used in the following visualizations: bar chart, bar gauge, candlestick, canvas, gauge, geomap, histogram, pie chart, stat, state timeline, status history, table, time series, trend +--- + +Value mapping is a technique you can use to change how data appears in a visualization. + +For each value mapping, set the following options: + +- **Condition** - Choose what's mapped to the display text and (optionally) color: + - **Value** - Specific values + - **Range** - Numerical ranges + - **Regex** - Regular expressions + - **Special** - Special values like `Null`, `NaN` (not a number), or boolean values like `true` and `false` +- **Display text** +- **Color** (Optional) +- **Icon** (Canvas only) + +To learn more, refer to [Configure value mappings](../../configure-value-mappings/). From b77763bbcc48253f42e032fe89f7f778c947f093 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Fri, 26 Apr 2024 15:35:39 -0300 Subject: [PATCH 141/222] ShareModal: Differentiate between panel and dashboard share for tracking (#86992) --- .../dashboard-scene/sharing/ShareLinkTab.tsx | 3 ++- .../features/dashboard-scene/sharing/ShareModal.tsx | 3 ++- .../dashboard-scene/sharing/ShareSnapshotTab.tsx | 12 +++++++++--- .../dashboard/components/ShareModal/ShareEmbed.tsx | 5 +++-- .../dashboard/components/ShareModal/ShareExport.tsx | 11 +++++++++-- .../components/ShareModal/ShareLibraryPanel.tsx | 5 +++-- .../dashboard/components/ShareModal/ShareLink.tsx | 8 ++++---- .../dashboard/components/ShareModal/ShareModal.tsx | 3 ++- .../SharePublicDashboard.test.tsx | 5 ++++- .../components/ShareModal/ShareSnapshot.tsx | 3 +++ .../dashboard/components/ShareModal/utils.ts | 7 +++++++ .../AddLibraryPanelModal/AddLibraryPanelModal.tsx | 1 - 12 files changed, 48 insertions(+), 18 deletions(-) diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx index cf2e28c8299..7f3fc6a5e12 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx @@ -9,7 +9,7 @@ import { Alert, ClipboardButton, Field, FieldSet, Icon, Input, Switch } from '@g import { t, Trans } from 'app/core/internationalization'; import { createShortLink } from 'app/core/utils/shortLinks'; import { ThemePicker } from 'app/features/dashboard/components/ShareModal/ThemePicker'; -import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; +import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; import { DashboardInteractions } from '../utils/interactions'; import { getDashboardUrl } from '../utils/urlBuilders'; @@ -131,6 +131,7 @@ export class ShareLinkTab extends SceneObjectBase { currentTimeRange: this.state.useLockedTime, theme: this.state.selectedTheme, shortenURL: this.state.useShortUrl, + shareResource: getTrackingSource(this.state.panelRef), }); }; } diff --git a/public/app/features/dashboard-scene/sharing/ShareModal.tsx b/public/app/features/dashboard-scene/sharing/ShareModal.tsx index 3ada3d1fd72..86d6ab6976d 100644 --- a/public/app/features/dashboard-scene/sharing/ShareModal.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareModal.tsx @@ -7,6 +7,7 @@ import { contextSrv } from 'app/core/core'; import { t } from 'app/core/internationalization'; import { isPublicDashboardsEnabled } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils'; +import { getTrackingSource } from '../../dashboard/components/ShareModal/utils'; import { DashboardScene } from '../scene/DashboardScene'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { DashboardInteractions } from '../utils/interactions'; @@ -92,7 +93,7 @@ export class ShareModal extends SceneObjectBase implements Moda }; onChangeTab: ComponentProps['onChangeTab'] = (tab) => { - DashboardInteractions.sharingTabChanged({ item: tab.value }); + DashboardInteractions.sharingTabChanged({ item: tab.value, shareResource: getTrackingSource(this.state.panelRef) }); this.setState({ activeTab: tab.value }); }; } diff --git a/public/app/features/dashboard-scene/sharing/ShareSnapshotTab.tsx b/public/app/features/dashboard-scene/sharing/ShareSnapshotTab.tsx index bd733ecbd65..d0de8715791 100644 --- a/public/app/features/dashboard-scene/sharing/ShareSnapshotTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareSnapshotTab.tsx @@ -6,7 +6,7 @@ import { getBackendSrv } from '@grafana/runtime'; import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes'; import { Button, ClipboardButton, Field, Input, Modal, RadioButtonGroup } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; -import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; +import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; import { getDashboardSnapshotSrv, SnapshotSharingOptions } from 'app/features/dashboard/services/SnapshotSrv'; import { transformSceneToSaveModel, trimDashboardForSnapshot } from '../serialization/transformSceneToSaveModel'; @@ -124,9 +124,15 @@ export class ShareSnapshotTab extends SceneObjectBase { return await getDashboardSnapshotSrv().create(cmdData); } finally { if (external) { - DashboardInteractions.publishSnapshotClicked({ expires: cmdData.expires }); + DashboardInteractions.publishSnapshotClicked({ + expires: cmdData.expires, + shareResource: getTrackingSource(this.state.panelRef), + }); } else { - DashboardInteractions.publishSnapshotLocalClicked({ expires: cmdData.expires }); + DashboardInteractions.publishSnapshotLocalClicked({ + expires: cmdData.expires, + shareResource: getTrackingSource(this.state.panelRef), + }); } } }; diff --git a/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx b/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx index 8d60b1e4e3a..4ff8be1eaab 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx @@ -9,7 +9,7 @@ import { DashboardInteractions } from 'app/features/dashboard-scene/utils/intera import { ThemePicker } from './ThemePicker'; import { ShareModalTabProps } from './types'; -import { buildIframeHtml } from './utils'; +import { buildIframeHtml, getTrackingSource } from './utils'; interface Props extends Omit { panel?: { timeFrom?: string; id: number }; @@ -24,7 +24,7 @@ export function ShareEmbed({ panel, dashboard, range, buildIframe = buildIframeH const [iframeHtml, setIframeHtml] = useState(''); useEffectOnce(() => { - reportInteraction('grafana_dashboards_embed_share_viewed'); + reportInteraction('grafana_dashboards_embed_share_viewed', { shareResource: getTrackingSource(panel) }); }); useEffect(() => { @@ -85,6 +85,7 @@ export function ShareEmbed({ panel, dashboard, range, buildIframe = buildIframeH DashboardInteractions.embedSnippetCopy({ currentTimeRange: useCurrentTimeRange, theme: selectedTheme, + shareResource: getTrackingSource(panel), }); }} > diff --git a/public/app/features/dashboard/components/ShareModal/ShareExport.tsx b/public/app/features/dashboard/components/ShareModal/ShareExport.tsx index 6bf1bad1b84..6ba26550ce3 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareExport.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareExport.tsx @@ -10,6 +10,7 @@ import { ShowModalReactEvent } from 'app/types/events'; import { ViewJsonModal } from './ViewJsonModal'; import { ShareModalTabProps } from './types'; +import { getTrackingSource } from './utils'; interface Props extends ShareModalTabProps {} @@ -39,7 +40,10 @@ export class ShareExport extends PureComponent { const { dashboard } = this.props; const { shareExternally } = this.state; - DashboardInteractions.exportSaveJsonClicked({ externally: shareExternally }); + DashboardInteractions.exportSaveJsonClicked({ + externally: shareExternally, + shareResource: getTrackingSource(this.props.panel), + }); if (shareExternally) { this.exporter.makeExportable(dashboard).then((dashboardJson) => { @@ -53,7 +57,10 @@ export class ShareExport extends PureComponent { onViewJson = () => { const { dashboard } = this.props; const { shareExternally } = this.state; - DashboardInteractions.exportViewJsonClicked({ externally: shareExternally }); + DashboardInteractions.exportViewJsonClicked({ + externally: shareExternally, + shareResource: getTrackingSource(this.props.panel), + }); if (shareExternally) { this.exporter.makeExportable(dashboard).then((dashboardJson) => { diff --git a/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx b/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx index b40dce681db..9b76f04af65 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx @@ -5,6 +5,7 @@ import { Trans } from 'app/core/internationalization'; import { AddLibraryPanelContents } from 'app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal'; import { ShareModalTabProps } from './types'; +import { getTrackingSource } from './utils'; interface Props extends ShareModalTabProps { initialFolderUid?: string; @@ -12,8 +13,8 @@ interface Props extends ShareModalTabProps { export const ShareLibraryPanel = ({ panel, initialFolderUid, onDismiss }: Props) => { useEffect(() => { - reportInteraction('grafana_dashboards_library_panel_share_viewed'); - }, []); + reportInteraction('grafana_dashboards_library_panel_share_viewed', { shareResource: getTrackingSource(panel) }); + }, [panel]); if (!panel) { return null; diff --git a/public/app/features/dashboard/components/ShareModal/ShareLink.tsx b/public/app/features/dashboard/components/ShareModal/ShareLink.tsx index b7bc752eab9..595f0d5a6dd 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLink.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLink.tsx @@ -8,7 +8,7 @@ import { DashboardInteractions } from 'app/features/dashboard-scene/utils/intera import { ThemePicker } from './ThemePicker'; import { ShareModalTabProps } from './types'; -import { buildImageUrl, buildShareUrl } from './utils'; +import { buildImageUrl, buildShareUrl, getTrackingSource } from './utils'; export interface Props extends ShareModalTabProps {} @@ -30,7 +30,6 @@ export class ShareLink extends PureComponent { shareUrl: '', imageUrl: '', }; - this.onCopy = this.onCopy.bind(this); } componentDidMount() { @@ -74,13 +73,14 @@ export class ShareLink extends PureComponent { return this.state.shareUrl; }; - onCopy() { + onCopy = () => { DashboardInteractions.shareLinkCopied({ currentTimeRange: this.state.useCurrentTimeRange, theme: this.state.selectedTheme, shortenURL: this.state.useShortUrl, + shareResource: getTrackingSource(this.props.panel), }); - } + }; render() { const { panel, dashboard } = this.props; diff --git a/public/app/features/dashboard/components/ShareModal/ShareModal.tsx b/public/app/features/dashboard/components/ShareModal/ShareModal.tsx index ddbe9cfaa03..b50c79a1735 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareModal.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareModal.tsx @@ -16,7 +16,7 @@ import { ShareLibraryPanel } from './ShareLibraryPanel'; import { ShareLink } from './ShareLink'; import { ShareSnapshot } from './ShareSnapshot'; import { ShareModalTabModel } from './types'; -import { shareDashboardType } from './utils'; +import { getTrackingSource, shareDashboardType } from './utils'; const customDashboardTabs: ShareModalTabModel[] = []; const customPanelTabs: ShareModalTabModel[] = []; @@ -104,6 +104,7 @@ class UnthemedShareModal extends React.Component { this.setState((prevState) => ({ ...prevState, activeTab: t.value })); DashboardInteractions.sharingTabChanged({ item: t.value, + shareResource: getTrackingSource(this.props.panel), }); }; diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx index 8673036f63c..faff169478a 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx @@ -357,7 +357,10 @@ describe('SharePublic - Report interactions', () => { await waitFor(() => { expect(DashboardInteractions.sharingTabChanged).toHaveBeenCalledTimes(1); - expect(DashboardInteractions.sharingTabChanged).lastCalledWith({ item: shareDashboardType.publicDashboard }); + expect(DashboardInteractions.sharingTabChanged).lastCalledWith({ + item: shareDashboardType.publicDashboard, + shareResource: 'dashboard', + }); }); }); diff --git a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx index aa449768aa7..300cdbc29bf 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx @@ -12,6 +12,7 @@ import { VariableRefresh } from '../../../variables/types'; import { getDashboardSnapshotSrv } from '../../services/SnapshotSrv'; import { ShareModalTabProps } from './types'; +import { getTrackingSource } from './utils'; interface Props extends ShareModalTabProps {} @@ -118,11 +119,13 @@ export class ShareSnapshot extends PureComponent { DashboardInteractions.publishSnapshotClicked({ expires: snapshotExpires, timeout: timeoutSeconds, + shareResource: getTrackingSource(this.props.panel), }); } else { DashboardInteractions.publishSnapshotLocalClicked({ expires: snapshotExpires, timeout: timeoutSeconds, + shareResource: getTrackingSource(this.props.panel), }); } this.setState({ isLoading: false }); diff --git a/public/app/features/dashboard/components/ShareModal/utils.ts b/public/app/features/dashboard/components/ShareModal/utils.ts index 90b1664c492..12518e52eeb 100644 --- a/public/app/features/dashboard/components/ShareModal/utils.ts +++ b/public/app/features/dashboard/components/ShareModal/utils.ts @@ -1,5 +1,6 @@ import { dateTime, locationUtil, TimeRange, urlUtil, rangeUtil } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { SceneObjectRef, VizPanel } from '@grafana/scenes'; import { createShortLink } from 'app/core/utils/shortLinks'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; @@ -162,6 +163,12 @@ export function getLocalTimeZone() { return '&tz=' + encodeURIComponent(options.timeZone); } +export const getTrackingSource = ( + panel?: PanelModel | SceneObjectRef | { timeFrom?: string; id: number } +) => { + return panel ? 'panel' : 'dashboard'; +}; + export const shareDashboardType: { [key: string]: string; } = { diff --git a/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx b/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx index 73b086913c5..697b9197b6a 100644 --- a/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx +++ b/public/app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal.tsx @@ -23,7 +23,6 @@ export const AddLibraryPanelContents = ({ panel, initialFolderUid, onDismiss }: const [debouncedPanelName, setDebouncedPanelName] = useState(panel.title); const [waiting, setWaiting] = useState(false); - console.log('folderUid', folderUid); useEffect(() => setWaiting(true), [panelName]); useDebounce(() => setDebouncedPanelName(panelName), 350, [panelName]); From 7077a5850e75f7a33b7f2f8cebad2e0633b37e74 Mon Sep 17 00:00:00 2001 From: brendamuir <100768211+brendamuir@users.noreply.github.com> Date: Sat, 27 Apr 2024 09:59:42 +0200 Subject: [PATCH 142/222] Alerting docs: more vale (#86978) --- .../alerting/fundamentals/alert-rules/_index.md | 4 ++-- .../alert-rules/queries-conditions.md | 17 ++++++++--------- .../configure-alert-state-history/index.md | 12 ++++++------ .../configure-high-availability/_index.md | 16 ++++++++-------- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/docs/sources/alerting/fundamentals/alert-rules/_index.md b/docs/sources/alerting/fundamentals/alert-rules/_index.md index 8edc3eac916..43cbd746922 100644 --- a/docs/sources/alerting/fundamentals/alert-rules/_index.md +++ b/docs/sources/alerting/fundamentals/alert-rules/_index.md @@ -32,7 +32,7 @@ Grafana supports two different alert rule types: Grafana-managed alert rules and ## Grafana-managed alert rules -Grafana-managed alert rules are the most flexible alert rule type. They allow you to create alerts that can act on data from any of our [supported data sources](#supported-data-sources), and use multiple data sources in a single alert rule. +Grafana-managed alert rules are the most flexible alert rule type. They allow you to create alerts that can act on data from any of the [supported data sources](#supported-data-sources), and use multiple data sources in a single alert rule. Additionally, you can also add [expressions to transform your data][expression-queries], set custom alert conditions, and include [images in alert notifications][notification-images]. @@ -77,7 +77,7 @@ When choosing which alert rule type to use, consider the following comparison be |
Feature
|
Grafana-managed alert rule
|
Data source-managed alert rule | | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Create alert rules based on data from any of our supported data sources | Yes | No. You can only create alert rules that are based on Prometheus-based data. | +| Create alert rules based on data from any of the supported data sources | Yes | No. You can only create alert rules that are based on Prometheus-based data. | | Mix and match data sources | Yes | No | | Includes support for recording rules | No | Yes | | Add expressions to transform your data and set alert conditions | Yes | No | diff --git a/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md b/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md index 41e0f2d2101..762120f9e2b 100644 --- a/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md +++ b/docs/sources/alerting/fundamentals/alert-rules/queries-conditions.md @@ -20,17 +20,17 @@ weight: 104 # Queries and conditions -In Grafana, queries play a vital role in fetching and transforming data from supported data sources, which include databases like MySQL and PostgreSQL, time series databases like Prometheus, InfluxDB and Graphite, and services like Elasticsearch, AWS CloudWatch, Azure Monitor and Google Cloud Monitoring. +In Grafana, queries play a vital role in fetching and transforming data from supported data sources, which include databases like MySQL and PostgreSQL, time series databases like Prometheus, InfluxDB and Graphite, and services like Elasticsearch, Amazon CloudWatch, Azure Monitor, and Google Cloud Monitoring. -For more information on supported data sources, see [Data sources][data-source-alerting]. +For more information on supported data sources, refer to [Data sources][data-source-alerting]. -The process of executing a query involves defining the data source, specifying the desired data to retrieve, and applying relevant filters or transformations. Query languages or syntaxes specific to the chosen data source are utilized for constructing these queries. +The process of executing a query involves defining the data source, specifying the desired data to retrieve, and applying relevant filters or transformations. Query languages or syntax specific to the chosen data source are utilized for constructing these queries. In Alerting, you define a query to get the data you want to measure and a condition that needs to be met before an alert rule fires. An alert rule consists of one or more queries and expressions that select the data you want to measure. -For more information on queries and expressions, see [Query and transform data][query-transform-data]. +For more information on queries and expressions, refer to [Query and transform data][query-transform-data]. ## Data source queries @@ -123,7 +123,7 @@ These functions are available for **Reduce** and **Classic condition** expressio ## Alert condition -An alert condition is the query or expression that determines whether the alert will fire or not depending on the value it yields. There can be only one condition which will determine the triggering of the alert. +An alert condition is the query or expression that determines whether the alert fires or not depending on the value it yields. There can be only one condition which determines the triggering of the alert. After you have defined your queries and/or expressions, choose one of them as the alert rule condition. @@ -145,11 +145,11 @@ Grafana-managed alert rules are evaluated for a specific interval of time. Durin It can be tricky to create an alert rule for a noisy metric. That is, when the value of a metric continually goes above and below a threshold. This is called flapping and results in a series of firing - resolved - firing notifications and a noisy alert state history. -For example, if you have an alert for latency with a threshold of 1000ms and the number fluctuates around 1000 (say 980 ->1010 -> 990 -> 1020, and so on) then each of those will trigger a notification. +For example, if you have an alert for latency with a threshold of 1000ms and the number fluctuates around 1000 (say 980 ->1010 -> 990 -> 1020, and so on) then each of those triggers a notification. To solve this problem, you can set a (custom) recovery threshold, which basically means having two thresholds instead of one. An alert is triggered when the first threshold is crossed and is resolved only when the second threshold is crossed. -For example, you could set a threshold of 1000ms and a recovery threshold of 900ms. This way, an alert rule will only stop firing when it goes under 900ms and flapping is reduced. +For example, you could set a threshold of 1000ms and a recovery threshold of 900ms. This way, an alert rule only stops firing when it goes under 900ms and flapping is reduced. ## Alert on numeric data @@ -179,7 +179,6 @@ For a MySQL table called "DiskSpace": | 2021-June-7 | web1 | /etc | 3 | | 2021-June-7 | web2 | /var | 4 | | 2021-June-7 | web3 | /var | 8 | -| ... | ... | ... | ... | You can query the data filtering on time, but without returning the time series to Grafana. For example, an alert that would trigger per Host, Disk when there is less than 5% free space: @@ -204,7 +203,7 @@ This query returns the following Table response to Grafana: | web2 | /var | 4 | | web3 | /var | 0 | -When this query is used as the **condition** in an alert rule, then the non-zero will be alerting. As a result, three alert instances are produced: +When this query is used as the **condition** in an alert rule, then the non-zero is alerting. As a result, three alert instances are produced: | Labels | Status | | --------------------- | -------- | diff --git a/docs/sources/alerting/set-up/configure-alert-state-history/index.md b/docs/sources/alerting/set-up/configure-alert-state-history/index.md index a9892dc716b..5ce753a6ad7 100644 --- a/docs/sources/alerting/set-up/configure-alert-state-history/index.md +++ b/docs/sources/alerting/set-up/configure-alert-state-history/index.md @@ -18,17 +18,17 @@ weight: 250 Starting with Grafana 10, Alerting can record all alert rule state changes for your Grafana managed alert rules in a Loki instance. -This allows you to explore the behavior of your alert rules in the Grafana explore view and levels up the existing state history modal with a powerful new visualisation. +This allows you to explore the behavior of your alert rules in the Grafana explore view and levels up the existing state history dialog box with a powerful new visualisation. ## Configuring Loki -To set up alert state history, make sure to have a Loki instance Grafana can write data to. The default settings might need some tweaking as the state history modal might query up to 30 days of data. +To set up alert state history, make sure to have a Loki instance Grafana can write data to. The default settings might need some tweaking as the state history dialog box might query up to 30 days of data. -The following change to the default configuration should work for most instances, but we recommend looking at the full Loki configuration settings and adjust according to your needs. +The following change to the default configuration should work for most instances, but look at the full Loki configuration settings and adjust according to your needs. -As this might impact the performances of an existing Loki instance, we recommend using a separate Loki instance for the alert state history. +As this might impact the performances of an existing Loki instance, use a separate Loki instance for the alert state history. ```yaml limits_config: @@ -38,7 +38,7 @@ limits_config: ## Configuring Grafana -We need some additional configuration in the Grafana configuration file to have it working with the alert state history. +Additional configuration is required in the Grafana configuration file to have it working with the alert state history. The example below instructs Grafana to write alert state history to a local Loki instance: @@ -56,7 +56,7 @@ enable = alertStateHistoryLokiSecondary, alertStateHistoryLokiPrimary, alertStat ## Adding the Loki data source -See our instructions on [adding a data source](/docs/grafana/latest/administration/data-source-management/). +Refer to the instructions on [adding a data source](/docs/grafana/latest/administration/data-source-management/). ## Querying the history diff --git a/docs/sources/alerting/set-up/configure-high-availability/_index.md b/docs/sources/alerting/set-up/configure-high-availability/_index.md index 4992fbbe9eb..04594a87554 100644 --- a/docs/sources/alerting/set-up/configure-high-availability/_index.md +++ b/docs/sources/alerting/set-up/configure-high-availability/_index.md @@ -26,7 +26,7 @@ Grafana Alerting uses the Prometheus model of separating the evaluation of alert {{< figure src="/static/img/docs/alerting/unified/high-availability-ua.png" class="docs-image--no-shadow" max-width= "750px" caption="High availability" >}} -When running multiple instances of Grafana, all alert rules are evaluated on all instances. You can think of the evaluation of alert rules as being duplicated by the number of running Grafana instances. This is how Grafana Alerting makes sure that as long as at least one Grafana instance is working, alert rules will still be evaluated and notifications for alerts will still be sent. +When running multiple instances of Grafana, all alert rules are evaluated on all instances. You can think of the evaluation of alert rules as being duplicated by the number of running Grafana instances. This is how Grafana Alerting makes sure that as long as at least one Grafana instance is working, alert rules are still be evaluated and notifications for alerts are still sent. You can find this duplication in state history and it is a good way to confirm if you are using high availability. @@ -36,8 +36,8 @@ The Alertmanager uses a gossip protocol to share information about notifications {{% admonition type="note" %}} -If using a mix of `execute_alerts=false` and `execute_alerts=true` on the HA nodes, since the alert state is not shared amongst the Grafana instances, the instances with `execute_alerts=false` will not show any alert status. -This is because the HA settings (`ha_peers`, etc), only apply to the alert notification delivery (i.e. de-duplication of alert notifications, and silences, as mentioned above). +If using a mix of `execute_alerts=false` and `execute_alerts=true` on the HA nodes, since the alert state is not shared amongst the Grafana instances, the instances with `execute_alerts=false` do not show any alert status. +This is because the HA settings (`ha_peers`, etc) only apply to the alert notification delivery (i.e. de-duplication of alert notifications, and silences, as mentioned above). {{% /admonition %}} @@ -61,13 +61,13 @@ Since gossiping of notifications and silences uses both TCP and UDP port `9094`, As an alternative to Memberlist, you can use Redis for high availability. This is useful if you want to have a central database for HA and cannot support the meshing of all Grafana servers. -1. Make sure you have a redis server that supports pub/sub. If you use a proxy in front of your redis cluster, make sure the proxy supports pub/sub. +1. Make sure you have a redis server that supports pub/sub. If you use a proxy in front of your Redis cluster, make sure the proxy supports pub/sub. 1. In your custom configuration file ($WORKING_DIR/conf/custom.ini), go to the [unified_alerting] section. -1. Set `ha_redis_address` to the redis server address Grafana should connect to. +1. Set `ha_redis_address` to the Redis server address Grafana should connect to. 1. [Optional] Set the username and password if authentication is enabled on the redis server using `ha_redis_username` and `ha_redis_password`. 1. [Optional] Set `ha_redis_prefix` to something unique if you plan to share the redis server with multiple Grafana instances. -The following metrics can be used for meta monitoring, exposed by Grafana's `/metrics` endpoint: +The following metrics can be used for meta monitoring, exposed by the `/metrics` endpoint in Grafana: | Metric | Description | | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | @@ -83,7 +83,7 @@ The following metrics can be used for meta monitoring, exposed by Grafana's `/me ## Enable alerting high availability using Kubernetes -1. You can expose the pod IP [through an environment variable](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/) via the container definition. +1. You can expose the Pod IP [through an environment variable](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/) via the container definition. ```yaml env: @@ -115,7 +115,7 @@ The following metrics can be used for meta monitoring, exposed by Grafana's `/me fieldPath: status.podIP ``` -1. Create a headless service that returns the pod IP instead of the service IP, which is what the `ha_peers` need: +1. Create a headless service that returns the Pod IP instead of the service IP, which is what the `ha_peers` need: ```yaml apiVersion: v1 From c4cfee8d9661b522e3fbf6c9b84576a455c6199b Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Mon, 29 Apr 2024 08:53:05 +0200 Subject: [PATCH 143/222] User: support setting org and help flags though update function (#86535) * User: Support setting active org through update function * User: add support to update help flags through update function --- pkg/api/org_invite.go | 2 +- pkg/api/user.go | 30 +--- pkg/middleware/org_redirect.go | 3 +- pkg/middleware/org_redirect_test.go | 2 +- pkg/services/authn/authnimpl/sync/org_sync.go | 8 +- .../authn/authnimpl/sync/org_sync_test.go | 8 +- pkg/services/user/model.go | 20 +-- pkg/services/user/user.go | 2 - pkg/services/user/userimpl/store.go | 130 +++++----------- pkg/services/user/userimpl/store_test.go | 147 +++++------------- pkg/services/user/userimpl/user.go | 46 +++--- pkg/services/user/userimpl/user_test.go | 110 ++++++------- pkg/services/user/usertest/fake.go | 8 - pkg/services/user/usertest/mock.go | 36 ----- 14 files changed, 179 insertions(+), 373 deletions(-) diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go index 51054a22ada..ee41146eacf 100644 --- a/pkg/api/org_invite.go +++ b/pkg/api/org_invite.go @@ -338,7 +338,7 @@ func (hs *HTTPServer) applyUserInvite(ctx context.Context, usr *user.User, invit if setActive { // set org to active - if err := hs.userService.SetUsingOrg(ctx, &user.SetUsingOrgCommand{OrgID: invite.OrgID, UserID: usr.ID}); err != nil { + if err := hs.userService.Update(ctx, &user.UpdateUserCommand{OrgID: &invite.OrgID, UserID: usr.ID}); err != nil { return false, response.Error(http.StatusInternalServerError, "Failed to set org as active", err) } } diff --git a/pkg/api/user.go b/pkg/api/user.go index f774d8ae133..d64ff7a15ca 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -215,9 +215,7 @@ func (hs *HTTPServer) UpdateUserActiveOrg(c *contextmodel.ReqContext) response.R return response.Error(http.StatusUnauthorized, "Not a valid organization", nil) } - cmd := user.SetUsingOrgCommand{UserID: userID, OrgID: orgID} - - if err := hs.userService.SetUsingOrg(c.Req.Context(), &cmd); err != nil { + if err := hs.userService.Update(c.Req.Context(), &user.UpdateUserCommand{UserID: userID, OrgID: &orgID}); err != nil { return response.Error(http.StatusInternalServerError, "Failed to change active organization", err) } @@ -493,9 +491,7 @@ func (hs *HTTPServer) UserSetUsingOrg(c *contextmodel.ReqContext) response.Respo return response.Error(http.StatusUnauthorized, "Not a valid organization", nil) } - cmd := user.SetUsingOrgCommand{UserID: userID, OrgID: orgID} - - if err := hs.userService.SetUsingOrg(c.Req.Context(), &cmd); err != nil { + if err := hs.userService.Update(c.Req.Context(), &user.UpdateUserCommand{UserID: userID, OrgID: &orgID}); err != nil { return response.Error(http.StatusInternalServerError, "Failed to change active organization", err) } @@ -527,8 +523,7 @@ func (hs *HTTPServer) ChangeActiveOrgAndRedirectToHome(c *contextmodel.ReqContex return } - cmd := user.SetUsingOrgCommand{UserID: userID, OrgID: orgID} - if err := hs.userService.SetUsingOrg(c.Req.Context(), &cmd); err != nil { + if err := hs.userService.Update(c.Req.Context(), &user.UpdateUserCommand{UserID: userID, OrgID: &orgID}); err != nil { hs.NotFoundHandler(c) return } @@ -606,16 +601,11 @@ func (hs *HTTPServer) SetHelpFlag(c *contextmodel.ReqContext) response.Response bitmask := &usr.HelpFlags1 bitmask.AddFlag(user.HelpFlags1(flag)) - cmd := user.SetUserHelpFlagCommand{ - UserID: userID, - HelpFlags1: *bitmask, - } - - if err := hs.userService.SetUserHelpFlag(c.Req.Context(), &cmd); err != nil { + if err := hs.userService.Update(c.Req.Context(), &user.UpdateUserCommand{UserID: userID, HelpFlags1: bitmask}); err != nil { return response.Error(http.StatusInternalServerError, "Failed to update help flag", err) } - return response.JSON(http.StatusOK, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1}) + return response.JSON(http.StatusOK, &util.DynMap{"message": "Help flag set", "helpFlags1": *bitmask}) } // swagger:route GET /user/helpflags/clear signed_in_user clearHelpFlags @@ -633,16 +623,12 @@ func (hs *HTTPServer) ClearHelpFlags(c *contextmodel.ReqContext) response.Respon return errResponse } - cmd := user.SetUserHelpFlagCommand{ - UserID: userID, - HelpFlags1: user.HelpFlags1(0), - } - - if err := hs.userService.SetUserHelpFlag(c.Req.Context(), &cmd); err != nil { + flags := user.HelpFlags1(0) + if err := hs.userService.Update(c.Req.Context(), &user.UpdateUserCommand{UserID: userID, HelpFlags1: &flags}); err != nil { return response.Error(http.StatusInternalServerError, "Failed to update help flag", err) } - return response.JSON(http.StatusOK, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1}) + return response.JSON(http.StatusOK, &util.DynMap{"message": "Help flag set", "helpFlags1": flags}) } func getUserID(c *contextmodel.ReqContext) (int64, *response.NormalResponse) { diff --git a/pkg/middleware/org_redirect.go b/pkg/middleware/org_redirect.go index 1199f1852cc..36a1081f415 100644 --- a/pkg/middleware/org_redirect.go +++ b/pkg/middleware/org_redirect.go @@ -32,8 +32,7 @@ func OrgRedirect(cfg *setting.Cfg, userSvc user.Service) web.Handler { return } - cmd := user.SetUsingOrgCommand{UserID: ctx.UserID, OrgID: orgId} - if err := userSvc.SetUsingOrg(ctx.Req.Context(), &cmd); err != nil { + if err := userSvc.Update(ctx.Req.Context(), &user.UpdateUserCommand{UserID: ctx.UserID, OrgID: &orgId}); err != nil { if ctx.IsApiRequest() { ctx.JsonApiErr(404, "Not found", nil) } else { diff --git a/pkg/middleware/org_redirect_test.go b/pkg/middleware/org_redirect_test.go index 23e1ebb412d..e2bc6d81075 100644 --- a/pkg/middleware/org_redirect_test.go +++ b/pkg/middleware/org_redirect_test.go @@ -55,7 +55,7 @@ func TestOrgRedirectMiddleware(t *testing.T) { middlewareScenario(t, "when setting an invalid org for user", func(t *testing.T, sc *scenarioContext) { sc.withIdentity(&authn.Identity{}) - sc.userService.ExpectedSetUsingOrgError = fmt.Errorf("") + sc.userService.ExpectedError = fmt.Errorf("") sc.m.Get("/", sc.defaultHandler) sc.fakeReq("GET", "/?orgId=1").exec() diff --git a/pkg/services/authn/authnimpl/sync/org_sync.go b/pkg/services/authn/authnimpl/sync/org_sync.go index 8492e44de09..38429a07704 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync.go +++ b/pkg/services/authn/authnimpl/sync/org_sync.go @@ -120,9 +120,9 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a if _, ok := id.OrgRoles[id.OrgID]; !ok { if len(orgIDs) > 0 { id.OrgID = orgIDs[0] - return s.userService.SetUsingOrg(ctx, &user.SetUsingOrgCommand{ + return s.userService.Update(ctx, &user.UpdateUserCommand{ UserID: userID, - OrgID: id.OrgID, + OrgID: &id.OrgID, }) } } @@ -159,8 +159,8 @@ func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn. return } - cmd := user.SetUsingOrgCommand{UserID: userID, OrgID: s.cfg.LoginDefaultOrgId} - if svcErr := s.userService.SetUsingOrg(ctx, &cmd); svcErr != nil { + cmd := user.UpdateUserCommand{UserID: userID, OrgID: &s.cfg.LoginDefaultOrgId} + if svcErr := s.userService.Update(ctx, &cmd); svcErr != nil { ctxLogger.Error("Failed to set default org", "id", currentIdentity.ID, "err", svcErr) } } diff --git a/pkg/services/authn/authnimpl/sync/org_sync_test.go b/pkg/services/authn/authnimpl/sync/org_sync_test.go index 16c94171c4c..f8b774696d9 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/org_sync_test.go @@ -139,8 +139,8 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { defaultOrgSetting: 2, identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { - userService.On("SetUsingOrg", mock.Anything, mock.MatchedBy(func(cmd *user.SetUsingOrgCommand) bool { - return cmd.UserID == 1 && cmd.OrgID == 2 + userService.On("Update", mock.Anything, mock.MatchedBy(func(cmd *user.UpdateUserCommand) bool { + return cmd.UserID == 1 && *cmd.OrgID == 2 })).Return(nil) }, }, @@ -188,7 +188,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { defaultOrgSetting: 2, identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}, setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { - userService.On("SetUsingOrg", mock.Anything, mock.Anything).Return(fmt.Errorf("error")) + userService.On("Update", mock.Anything, mock.Anything).Return(fmt.Errorf("error")) }, }, } @@ -217,8 +217,6 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { } s.SetDefaultOrgHook(context.Background(), tt.identity, nil, tt.inputErr) - - userService.AssertExpectations(t) }) } } diff --git a/pkg/services/user/model.go b/pkg/services/user/model.go index 13aaed4ae78..73276afc8b6 100644 --- a/pkg/services/user/model.go +++ b/pkg/services/user/model.go @@ -39,7 +39,7 @@ type User struct { Company string EmailVerified bool Theme string - HelpFlags1 HelpFlags1 + HelpFlags1 HelpFlags1 `xorm:"help_flags1"` IsDisabled bool IsAdmin bool @@ -86,9 +86,13 @@ type UpdateUserCommand struct { IsDisabled *bool `json:"-"` EmailVerified *bool `json:"-"` IsGrafanaAdmin *bool `json:"-"` - - Password *Password `json:"-"` + // If password is included it will be validated, hashed and updated for user. + Password *Password `json:"-"` + // If old password is included it will be validated against users current password. OldPassword *Password `json:"-"` + // If OrgID is included update current org for user + OrgID *int64 `json:"-"` + HelpFlags1 *HelpFlags1 `json:"-"` } type UpdateUserLastSeenAtCommand struct { @@ -96,11 +100,6 @@ type UpdateUserLastSeenAtCommand struct { OrgID int64 } -type SetUsingOrgCommand struct { - UserID int64 - OrgID int64 -} - type SearchUsersQuery struct { SignedInUser identity.Requester OrgID int64 `xorm:"org_id"` @@ -179,11 +178,6 @@ type BatchDisableUsersCommand struct { IsDisabled bool } -type SetUserHelpFlagCommand struct { - HelpFlags1 HelpFlags1 - UserID int64 `xorm:"user_id"` -} - type GetSignedInUserQuery struct { UserID int64 `xorm:"user_id"` Login string diff --git a/pkg/services/user/user.go b/pkg/services/user/user.go index 06d8b113978..5d27249ac03 100644 --- a/pkg/services/user/user.go +++ b/pkg/services/user/user.go @@ -17,12 +17,10 @@ type Service interface { GetByEmail(context.Context, *GetUserByEmailQuery) (*User, error) Update(context.Context, *UpdateUserCommand) error UpdateLastSeenAt(context.Context, *UpdateUserLastSeenAtCommand) error - SetUsingOrg(context.Context, *SetUsingOrgCommand) error GetSignedInUserWithCacheCtx(context.Context, *GetSignedInUserQuery) (*SignedInUser, error) GetSignedInUser(context.Context, *GetSignedInUserQuery) (*SignedInUser, error) Search(context.Context, *SearchUsersQuery) (*SearchUserQueryResult, error) BatchDisableUsers(context.Context, *BatchDisableUsersCommand) error - SetUserHelpFlag(context.Context, *SetUserHelpFlagCommand) error GetProfile(context.Context, *GetUserProfileQuery) (*UserProfileDTO, error) } diff --git a/pkg/services/user/userimpl/store.go b/pkg/services/user/userimpl/store.go index ec8b6c8cf08..ea6aa5b69d3 100644 --- a/pkg/services/user/userimpl/store.go +++ b/pkg/services/user/userimpl/store.go @@ -19,20 +19,16 @@ import ( type store interface { Insert(context.Context, *user.User) (int64, error) - Get(context.Context, *user.User) (*user.User, error) GetByID(context.Context, int64) (*user.User, error) - GetNotServiceAccount(context.Context, int64) (*user.User, error) + GetByLogin(context.Context, *user.GetUserByLoginQuery) (*user.User, error) + GetByEmail(context.Context, *user.GetUserByEmailQuery) (*user.User, error) Delete(context.Context, int64) error LoginConflict(ctx context.Context, login, email string) error CaseInsensitiveLoginConflict(context.Context, string, string) error - GetByLogin(context.Context, *user.GetUserByLoginQuery) (*user.User, error) - GetByEmail(context.Context, *user.GetUserByEmailQuery) (*user.User, error) Update(context.Context, *user.UpdateUserCommand) error UpdateLastSeenAt(context.Context, *user.UpdateUserLastSeenAtCommand) error GetSignedInUser(context.Context, *user.GetSignedInUserQuery) (*user.SignedInUser, error) - UpdateUser(context.Context, *user.User) error GetProfile(context.Context, *user.GetUserProfileQuery) (*user.UserProfileDTO, error) - SetHelpFlag(context.Context, *user.SetUserHelpFlagCommand) error BatchDisableUsers(context.Context, *user.BatchDisableUsersCommand) error Search(context.Context, *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) Count(ctx context.Context) (int64, error) @@ -87,30 +83,6 @@ func (ss *sqlStore) Insert(ctx context.Context, cmd *user.User) (int64, error) { return cmd.ID, nil } -func (ss *sqlStore) Get(ctx context.Context, usr *user.User) (*user.User, error) { - ret := &user.User{} - err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { - // enforcement of lowercase due to forcement of caseinsensitive login - login := strings.ToLower(usr.Login) - email := strings.ToLower(usr.Email) - where := "email=? OR login=?" - - exists, err := sess.Where(where, email, login).Get(ret) - if !exists { - return user.ErrUserNotFound - } - if err != nil { - return err - } - return nil - }) - if err != nil { - return nil, err - } - - return ret, nil -} - func (ss *sqlStore) Delete(ctx context.Context, userID int64) error { err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { var rawSQL = "DELETE FROM " + ss.dialect.Quote("user") + " WHERE id = ?" @@ -123,21 +95,6 @@ func (ss *sqlStore) Delete(ctx context.Context, userID int64) error { return nil } -func (ss *sqlStore) GetNotServiceAccount(ctx context.Context, userID int64) (*user.User, error) { - usr := user.User{ID: userID} - err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { - has, err := sess.Where(ss.notServiceAccountFilter()).Get(&usr) - if err != nil { - return err - } - if !has { - return user.ErrUserNotFound - } - return nil - }) - return &usr, err -} - func (ss *sqlStore) GetByID(ctx context.Context, userID int64) (*user.User, error) { var usr user.User @@ -254,12 +211,12 @@ func (ss *sqlStore) GetByEmail(ctx context.Context, query *user.GetUserByEmailQu // sensitive. func (ss *sqlStore) LoginConflict(ctx context.Context, login, email string) error { err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { - return ss.loginConflict(ctx, sess, login, email) + return ss.loginConflict(sess, login, email) }) return err } -func (ss *sqlStore) loginConflict(ctx context.Context, sess *db.Session, login, email string) error { +func (ss *sqlStore) loginConflict(sess *db.Session, login, email string) error { users := make([]user.User, 0) where := "LOWER(email)=LOWER(?) OR LOWER(login)=LOWER(?)" login = strings.ToLower(login) @@ -289,52 +246,49 @@ func (ss *sqlStore) Update(ctx context.Context, cmd *user.UpdateUserCommand) err cmd.Email = strings.ToLower(cmd.Email) return ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - user := user.User{ + usr := user.User{ Name: cmd.Name, + Theme: cmd.Theme, Email: strings.ToLower(cmd.Email), Login: strings.ToLower(cmd.Login), - Theme: cmd.Theme, Updated: time.Now(), } q := sess.ID(cmd.UserID).Where(ss.notServiceAccountFilter()) - if cmd.Password != nil { - user.Password = *cmd.Password - } + setOptional(cmd.OrgID, func(v int64) { usr.OrgID = v }) + setOptional(cmd.Password, func(v user.Password) { usr.Password = v }) + setOptional(cmd.IsDisabled, func(v bool) { + q = q.UseBool("is_disabled") + usr.IsDisabled = v + }) + setOptional(cmd.EmailVerified, func(v bool) { + q = q.UseBool("email_verified") + usr.EmailVerified = v + }) + setOptional(cmd.IsGrafanaAdmin, func(v bool) { + q = q.UseBool("is_admin") + usr.IsAdmin = v + }) + setOptional(cmd.HelpFlags1, func(v user.HelpFlags1) { usr.HelpFlags1 = *cmd.HelpFlags1 }) - if cmd.IsDisabled != nil { - sess.UseBool("is_disabled") - user.IsDisabled = *cmd.IsDisabled - } - - if cmd.EmailVerified != nil { - q.UseBool("email_verified") - user.EmailVerified = *cmd.EmailVerified - } - - if cmd.IsGrafanaAdmin != nil { - q.UseBool("is_admin") - user.IsAdmin = *cmd.IsGrafanaAdmin - } - - if _, err := q.Update(&user); err != nil { + if _, err := q.Update(&usr); err != nil { return err } if cmd.IsGrafanaAdmin != nil && !*cmd.IsGrafanaAdmin { // validate that after update there is at least one server admin - if err := validateOneAdminLeft(ctx, sess); err != nil { + if err := validateOneAdminLeft(sess); err != nil { return err } } sess.PublishAfterCommit(&events.UserUpdated{ - Timestamp: user.Created, - Id: user.ID, - Name: user.Name, - Login: user.Login, - Email: user.Email, + Timestamp: usr.Created, + Id: usr.ID, + Name: usr.Name, + Login: usr.Login, + Email: usr.Email, }) return nil @@ -412,13 +366,6 @@ func (ss *sqlStore) GetSignedInUser(ctx context.Context, query *user.GetSignedIn return &signedInUser, err } -func (ss *sqlStore) UpdateUser(ctx context.Context, user *user.User) error { - return ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - _, err := sess.ID(user.ID).Update(user) - return err - }) -} - func (ss *sqlStore) GetProfile(ctx context.Context, query *user.GetUserProfileQuery) (*user.UserProfileDTO, error) { var usr user.User var userProfile user.UserProfileDTO @@ -450,19 +397,6 @@ func (ss *sqlStore) GetProfile(ctx context.Context, query *user.GetUserProfileQu return &userProfile, err } -func (ss *sqlStore) SetHelpFlag(ctx context.Context, cmd *user.SetUserHelpFlagCommand) error { - return ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - user := user.User{ - ID: cmd.UserID, - HelpFlags1: cmd.HelpFlags1, - Updated: time.Now(), - } - - _, err := sess.ID(cmd.UserID).Cols("help_flags1").Update(&user) - return err - }) -} - func (ss *sqlStore) Count(ctx context.Context) (int64, error) { type result struct { Count int64 @@ -501,7 +435,7 @@ func (ss *sqlStore) CountUserAccountsWithEmptyRole(ctx context.Context) (int64, } // validateOneAdminLeft validate that there is an admin user left -func validateOneAdminLeft(ctx context.Context, sess *db.Session) error { +func validateOneAdminLeft(sess *db.Session) error { count, err := sess.Where("is_admin=?", true).Count(&user.User{}) if err != nil { return err @@ -673,3 +607,9 @@ func (ss *sqlStore) getAnyUserType(ctx context.Context, userID int64) (*user.Use }) return &usr, err } + +func setOptional[T any](v *T, add func(v T)) { + if v != nil { + add(*v) + } +} diff --git a/pkg/services/user/userimpl/store_test.go b/pkg/services/user/userimpl/store_test.go index 5cc3731c2a7..5f768320051 100644 --- a/pkg/services/user/userimpl/store_test.go +++ b/pkg/services/user/userimpl/store_test.go @@ -27,81 +27,6 @@ func TestMain(m *testing.M) { testsuite.Run(m) } -func TestIntegrationUserGet(t *testing.T) { - testCases := []struct { - name string - wantErr error - searchLogin string - searchEmail string - }{ - { - name: "user found non exact", - wantErr: nil, - searchLogin: "test", - searchEmail: "Test@email.com", - }, - { - name: "user found exact", - wantErr: nil, - searchLogin: "test", - searchEmail: "test@email.com", - }, - { - name: "user found exact - case insensitive", - wantErr: nil, - searchLogin: "Test", - searchEmail: "Test@email.com", - }, - { - name: "user not found - case insensitive", - wantErr: user.ErrUserNotFound, - searchLogin: "Test_login", - searchEmail: "Test*@email.com", - }, - } - - if testing.Short() { - t.Skip("skipping integration test") - } - - ss, cfg := db.InitTestDBWithCfg(t) - userStore := ProvideStore(ss, cfg) - - _, errUser := userStore.Insert(context.Background(), - &user.User{ - Email: "test@email.com", - Name: "test", - Login: "test", - Created: time.Now(), - Updated: time.Now(), - }, - ) - require.NoError(t, errUser) - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - if db.IsTestDbMySQL() { - t.Skip("mysql is always case insensitive") - } - usr, err := userStore.Get(context.Background(), - &user.User{ - Email: tc.searchEmail, - Login: tc.searchLogin, - }, - ) - - if tc.wantErr != nil { - require.Error(t, err) - require.Nil(t, usr) - } else { - require.NoError(t, err) - require.NotNil(t, usr) - require.NotEmpty(t, usr.UID) - } - }) - } -} - func TestIntegrationUserDataAccess(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") @@ -120,12 +45,8 @@ func TestIntegrationUserDataAccess(t *testing.T) { } t.Run("user not found", func(t *testing.T) { - _, err := userStore.Get(context.Background(), - &user.User{ - Email: "test@email.com", - Name: "test1", - Login: "test1", - }, + _, err := userStore.GetByEmail(context.Background(), + &user.GetUserByEmailQuery{Email: "test@email.com"}, ) require.Error(t, err, user.ErrUserNotFound) }) @@ -143,6 +64,13 @@ func TestIntegrationUserDataAccess(t *testing.T) { require.NoError(t, err) }) + t.Run("get user", func(t *testing.T) { + _, err := userStore.GetByEmail(context.Background(), + &user.GetUserByEmailQuery{Email: "test@email.com"}, + ) + require.NoError(t, err) + }) + t.Run("insert user (with known UID)", func(t *testing.T) { ctx := context.Background() id, err := userStore.Insert(ctx, @@ -169,17 +97,6 @@ func TestIntegrationUserDataAccess(t *testing.T) { require.Equal(t, "abcd", siu.UserUID) }) - t.Run("get user", func(t *testing.T) { - _, err := userStore.Get(context.Background(), - &user.User{ - Email: "test@email.com", - Name: "test1", - Login: "test1", - }, - ) - require.NoError(t, err) - }) - t.Run("Testing DB - creates and loads user", func(t *testing.T) { ss := db.InitTestDB(t) _, usrSvc := createOrgAndUserSvc(t, ss, cfg) @@ -458,15 +375,6 @@ func TestIntegrationUserDataAccess(t *testing.T) { } }) - t.Run("update user", func(t *testing.T) { - err := userStore.UpdateUser(context.Background(), &user.User{ID: 1, Name: "testtestest", Login: "loginloginlogin"}) - require.NoError(t, err) - result, err := userStore.GetByID(context.Background(), 1) - require.NoError(t, err) - assert.Equal(t, result.Name, "testtestest") - assert.Equal(t, result.Login, "loginloginlogin") - }) - t.Run("Testing DB - grafana admin users", func(t *testing.T) { ss := db.InitTestDB(t) _, usrSvc := createOrgAndUserSvc(t, ss, cfg) @@ -483,7 +391,7 @@ func TestIntegrationUserDataAccess(t *testing.T) { UserID: usr.ID, IsGrafanaAdmin: boolPtr(false), }) - require.ErrorIs(t, user.ErrLastGrafanaAdmin, err) + require.ErrorIs(t, err, user.ErrLastGrafanaAdmin) usr, err = userStore.GetByID(context.Background(), usr.ID) require.NoError(t, err) @@ -518,9 +426,28 @@ func TestIntegrationUserDataAccess(t *testing.T) { require.NoError(t, err) }) - t.Run("SetHelpFlag", func(t *testing.T) { - err := userStore.SetHelpFlag(context.Background(), &user.SetUserHelpFlagCommand{UserID: 1, HelpFlags1: user.HelpFlags1(1)}) + t.Run("Update HelpFlags", func(t *testing.T) { + id, err := userStore.Insert(context.Background(), &user.User{ + Email: "help@test.com", + Name: "help", + Login: "help", + Updated: time.Now(), + Created: time.Now(), + LastSeenAt: time.Now(), + }) require.NoError(t, err) + original, err := userStore.GetByID(context.Background(), id) + require.NoError(t, err) + + helpflags := user.HelpFlags1(1) + err = userStore.Update(context.Background(), &user.UpdateUserCommand{UserID: id, HelpFlags1: &helpflags}) + require.NoError(t, err) + + got, err := userStore.GetByID(context.Background(), id) + require.NoError(t, err) + + original.HelpFlags1 = helpflags + assertEqualUser(t, original, got) }) t.Run("Testing DB - return list users based on their is_disabled flag", func(t *testing.T) { @@ -1013,6 +940,18 @@ func TestMetricsUsage(t *testing.T) { }) } +func assertEqualUser(t *testing.T, expected, got *user.User) { + // zero out time fields + expected.Updated = time.Time{} + expected.Created = time.Time{} + expected.LastSeenAt = time.Time{} + got.Updated = time.Time{} + got.Created = time.Time{} + got.LastSeenAt = time.Time{} + + assert.Equal(t, expected, got) +} + func createOrgAndUserSvc(t *testing.T, store db.DB, cfg *setting.Cfg) (org.Service, user.Service) { t.Helper() diff --git a/pkg/services/user/userimpl/user.go b/pkg/services/user/userimpl/user.go index 9a085c39ccb..8c885e4695e 100644 --- a/pkg/services/user/userimpl/user.go +++ b/pkg/services/user/userimpl/user.go @@ -202,7 +202,7 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use } func (s *Service) Delete(ctx context.Context, cmd *user.DeleteUserCommand) error { - _, err := s.store.GetNotServiceAccount(ctx, cmd.UserID) + _, err := s.store.GetByID(ctx, cmd.UserID) if err != nil { return err } @@ -251,6 +251,24 @@ func (s *Service) Update(ctx context.Context, cmd *user.UpdateUserCommand) error cmd.Password = &hashed } + if cmd.OrgID != nil { + orgs, err := s.orgService.GetUserOrgList(ctx, &org.GetUserOrgListQuery{UserID: cmd.UserID}) + if err != nil { + return err + } + + valid := false + for _, org := range orgs { + if org.OrgID == *cmd.OrgID { + valid = true + } + } + + if !valid { + return fmt.Errorf("user does not belong to org") + } + } + return s.store.Update(ctx, cmd) } @@ -275,28 +293,6 @@ func shouldUpdateLastSeen(t time.Time) bool { return time.Since(t) > time.Minute*5 } -func (s *Service) SetUsingOrg(ctx context.Context, cmd *user.SetUsingOrgCommand) error { - getOrgsForUserCmd := &org.GetUserOrgListQuery{UserID: cmd.UserID} - orgsForUser, err := s.orgService.GetUserOrgList(ctx, getOrgsForUserCmd) - if err != nil { - return err - } - - valid := false - for _, other := range orgsForUser { - if other.OrgID == cmd.OrgID { - valid = true - } - } - if !valid { - return fmt.Errorf("user does not belong to org") - } - return s.store.UpdateUser(ctx, &user.User{ - ID: cmd.UserID, - OrgID: cmd.OrgID, - }) -} - func (s *Service) GetSignedInUserWithCacheCtx(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) { var signedInUser *user.SignedInUser @@ -350,10 +346,6 @@ func (s *Service) BatchDisableUsers(ctx context.Context, cmd *user.BatchDisableU return s.store.BatchDisableUsers(ctx, cmd) } -func (s *Service) SetUserHelpFlag(ctx context.Context, cmd *user.SetUserHelpFlagCommand) error { - return s.store.SetHelpFlag(ctx, cmd) -} - func (s *Service) GetProfile(ctx context.Context, query *user.GetUserProfileQuery) (*user.UserProfileDTO, error) { result, err := s.store.GetProfile(ctx, query) return result, err diff --git a/pkg/services/user/userimpl/user_test.go b/pkg/services/user/userimpl/user_test.go index 6e362ae92ab..0b9a90b854b 100644 --- a/pkg/services/user/userimpl/user_test.go +++ b/pkg/services/user/userimpl/user_test.go @@ -126,58 +126,82 @@ func TestUserService(t *testing.T) { assert.Equal(t, query2.OrgID, result2.OrgID) }) - t.Run("Can set using org", func(t *testing.T) { - cmd := user.SetUsingOrgCommand{UserID: 2, OrgID: 1} - orgService.ExpectedUserOrgDTO = []*org.UserOrgDTO{{OrgID: 1}} + t.Run("SignedInUserQuery with a different org", func(t *testing.T) { + query := user.GetSignedInUserQuery{UserID: 2} + userStore.ExpectedSignedInUser = &user.SignedInUser{ + OrgID: 1, + Email: "ac2@test.com", + Name: "ac2 name", + Login: "ac2", + OrgName: "ac1@test.com", + } userStore.ExpectedError = nil - err := userService.SetUsingOrg(context.Background(), &cmd) + queryResult, err := userService.GetSignedInUser(context.Background(), &query) + require.NoError(t, err) - - t.Run("SignedInUserQuery with a different org", func(t *testing.T) { - query := user.GetSignedInUserQuery{UserID: 2} - userStore.ExpectedSignedInUser = &user.SignedInUser{ - OrgID: 1, - Email: "ac2@test.com", - Name: "ac2 name", - Login: "ac2", - OrgName: "ac1@test.com", - } - queryResult, err := userService.GetSignedInUser(context.Background(), &query) - - require.NoError(t, err) - require.EqualValues(t, queryResult.OrgID, 1) - require.Equal(t, queryResult.Email, "ac2@test.com") - require.Equal(t, queryResult.Name, "ac2 name") - require.Equal(t, queryResult.Login, "ac2") - require.Equal(t, queryResult.OrgName, "ac1@test.com") - }) + require.EqualValues(t, queryResult.OrgID, 1) + require.Equal(t, queryResult.Email, "ac2@test.com") + require.Equal(t, queryResult.Name, "ac2 name") + require.Equal(t, queryResult.Login, "ac2") + require.Equal(t, queryResult.OrgName, "ac1@test.com") }) } func TestService_Update(t *testing.T) { - t.Run("should return error if old password does not match stored password", func(t *testing.T) { - stored, err := user.Password("test").Hash("salt") - require.NoError(t, err) - service := &Service{store: &FakeUserStore{ExpectedUser: &user.User{Password: stored, Salt: "salt"}}} + setup := func(opts ...func(svc *Service)) *Service { + service := &Service{store: &FakeUserStore{}} + for _, o := range opts { + o(service) + } + return service + } - err = service.Update(context.Background(), &user.UpdateUserCommand{ - OldPassword: passwordPtr("test123"), + t.Run("should return error if old password does not match stored password", func(t *testing.T) { + service := setup(func(svc *Service) { + stored, err := user.Password("test").Hash("salt") + require.NoError(t, err) + + svc.store = &FakeUserStore{ExpectedUser: &user.User{Password: stored, Salt: "salt"}} }) + err := service.Update(context.Background(), &user.UpdateUserCommand{ + OldPassword: passwordPtr("test123"), + }) assert.ErrorIs(t, err, user.ErrPasswordMissmatch) }) t.Run("should return error new password is not valid", func(t *testing.T) { - stored, err := user.Password("test").Hash("salt") - require.NoError(t, err) - service := &Service{cfg: setting.NewCfg(), store: &FakeUserStore{ExpectedUser: &user.User{Password: stored, Salt: "salt"}}} + service := setup(func(svc *Service) { + stored, err := user.Password("test").Hash("salt") + require.NoError(t, err) + svc.cfg = setting.NewCfg() + svc.store = &FakeUserStore{ExpectedUser: &user.User{Password: stored, Salt: "salt"}} + }) - err = service.Update(context.Background(), &user.UpdateUserCommand{ + err := service.Update(context.Background(), &user.UpdateUserCommand{ OldPassword: passwordPtr("test"), Password: passwordPtr("asd"), }) require.ErrorIs(t, err, user.ErrPasswordTooShort) }) + + t.Run("Can set using org", func(t *testing.T) { + orgID := int64(1) + service := setup(func(svc *Service) { + svc.orgService = &orgtest.FakeOrgService{ExpectedUserOrgDTO: []*org.UserOrgDTO{{OrgID: orgID}}} + }) + err := service.Update(context.Background(), &user.UpdateUserCommand{UserID: 2, OrgID: &orgID}) + require.NoError(t, err) + }) + + t.Run("Cannot set using org when user is not member of it", func(t *testing.T) { + orgID := int64(1) + service := setup(func(svc *Service) { + svc.orgService = &orgtest.FakeOrgService{ExpectedUserOrgDTO: []*org.UserOrgDTO{{OrgID: 2}}} + }) + err := service.Update(context.Background(), &user.UpdateUserCommand{UserID: 2, OrgID: &orgID}) + require.Error(t, err) + }) } func TestMetrics(t *testing.T) { @@ -220,10 +244,6 @@ func newUserStoreFake() *FakeUserStore { return &FakeUserStore{} } -func (f *FakeUserStore) Get(ctx context.Context, query *user.User) (*user.User, error) { - return f.ExpectedUser, f.ExpectedError -} - func (f *FakeUserStore) Insert(ctx context.Context, query *user.User) (int64, error) { return 0, f.ExpectedError } @@ -232,10 +252,6 @@ func (f *FakeUserStore) Delete(ctx context.Context, userID int64) error { return f.ExpectedDeleteUserError } -func (f *FakeUserStore) GetNotServiceAccount(ctx context.Context, userID int64) (*user.User, error) { - return f.ExpectedUser, f.ExpectedError -} - func (f *FakeUserStore) GetByID(context.Context, int64) (*user.User, error) { return f.ExpectedUser, f.ExpectedError } @@ -268,22 +284,10 @@ func (f *FakeUserStore) GetSignedInUser(ctx context.Context, query *user.GetSign return f.ExpectedSignedInUser, f.ExpectedError } -func (f *FakeUserStore) UpdateUser(ctx context.Context, user *user.User) error { - return f.ExpectedError -} - func (f *FakeUserStore) GetProfile(ctx context.Context, query *user.GetUserProfileQuery) (*user.UserProfileDTO, error) { return f.ExpectedUserProfile, f.ExpectedError } -func (f *FakeUserStore) SetHelpFlag(ctx context.Context, cmd *user.SetUserHelpFlagCommand) error { - return f.ExpectedError -} - -func (f *FakeUserStore) UpdatePermissions(ctx context.Context, userID int64, isAdmin bool) error { - return f.ExpectedError -} - func (f *FakeUserStore) BatchDisableUsers(ctx context.Context, cmd *user.BatchDisableUsersCommand) error { return f.ExpectedError } diff --git a/pkg/services/user/usertest/fake.go b/pkg/services/user/usertest/fake.go index b33a04ce552..1d6e84ab44b 100644 --- a/pkg/services/user/usertest/fake.go +++ b/pkg/services/user/usertest/fake.go @@ -71,10 +71,6 @@ func (f *FakeUserService) UpdateLastSeenAt(ctx context.Context, cmd *user.Update return f.ExpectedError } -func (f *FakeUserService) SetUsingOrg(ctx context.Context, cmd *user.SetUsingOrgCommand) error { - return f.ExpectedSetUsingOrgError -} - func (f *FakeUserService) GetSignedInUserWithCacheCtx(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) { return f.GetSignedInUser(ctx, query) } @@ -104,10 +100,6 @@ func (f *FakeUserService) BatchDisableUsers(ctx context.Context, cmd *user.Batch return f.ExpectedError } -func (f *FakeUserService) SetUserHelpFlag(ctx context.Context, cmd *user.SetUserHelpFlagCommand) error { - return f.ExpectedError -} - func (f *FakeUserService) GetProfile(ctx context.Context, query *user.GetUserProfileQuery) (*user.UserProfileDTO, error) { if f.ExpectedUserProfileDTO != nil { return f.ExpectedUserProfileDTO, f.ExpectedError diff --git a/pkg/services/user/usertest/mock.go b/pkg/services/user/usertest/mock.go index 7668bf3548f..01cfea90965 100644 --- a/pkg/services/user/usertest/mock.go +++ b/pkg/services/user/usertest/mock.go @@ -340,42 +340,6 @@ func (_m *MockService) Search(_a0 context.Context, _a1 *user.SearchUsersQuery) ( return r0, r1 } -// SetUserHelpFlag provides a mock function with given fields: _a0, _a1 -func (_m *MockService) SetUserHelpFlag(_a0 context.Context, _a1 *user.SetUserHelpFlagCommand) error { - ret := _m.Called(_a0, _a1) - - if len(ret) == 0 { - panic("no return value specified for SetUserHelpFlag") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *user.SetUserHelpFlagCommand) error); ok { - r0 = rf(_a0, _a1) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// SetUsingOrg provides a mock function with given fields: _a0, _a1 -func (_m *MockService) SetUsingOrg(_a0 context.Context, _a1 *user.SetUsingOrgCommand) error { - ret := _m.Called(_a0, _a1) - - if len(ret) == 0 { - panic("no return value specified for SetUsingOrg") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *user.SetUsingOrgCommand) error); ok { - r0 = rf(_a0, _a1) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // Update provides a mock function with given fields: _a0, _a1 func (_m *MockService) Update(_a0 context.Context, _a1 *user.UpdateUserCommand) error { ret := _m.Called(_a0, _a1) From fdc102358659385b1643de961a8ed0e2428e682a Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Mon, 29 Apr 2024 09:41:43 +0200 Subject: [PATCH 144/222] Dashboard scenes: Fix min interval not saving (#86962) * Fix min interval not saving * Add tests * Fix test --- .../transformSaveModelToScene.test.ts | 14 ++++++++++++++ .../transformSceneToSaveModel.test.ts | 7 +++++++ .../serialization/transformSceneToSaveModel.ts | 5 ++++- .../utils/createPanelDataProvider.ts | 1 + 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index f5e278bcd7f..2bdb35e8d69 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -417,6 +417,20 @@ describe('transformSaveModelToScene', () => { expect(vizPanel.state.hoverHeader).toEqual(true); }); + it('should initalize the VizPanel with min interval set', () => { + const panel = { + title: '', + type: 'test-plugin', + gridPos: { x: 0, y: 0, w: 12, h: 8 }, + interval: '20m', + }; + + const { vizPanel } = buildGridItemForTest(panel); + + const queryRunner = getQueryRunnerFor(vizPanel); + expect(queryRunner?.state.minInterval).toBe('20m'); + }); + it('should set PanelTimeRange when timeFrom or timeShift is present', () => { const panel = { type: 'test-plugin', diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts index 2574ff6b4a8..c12a7bf83ce 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts @@ -258,6 +258,13 @@ describe('transformSceneToSaveModel', () => { expect(saveModel.transparent).toBe(true); }); + it('interval', () => { + const gridItem = buildGridItemFromPanelSchema({ interval: '20m' }); + const saveModel = gridItemToPanel(gridItem); + + expect(saveModel.interval).toBe('20m'); + }); + it('With angular options', () => { const gridItem = buildGridItemFromPanelSchema({}); const vizPanel = gridItem.state.body as VizPanel; diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts index 384aede6ea0..6d918ba78cf 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts @@ -281,7 +281,7 @@ function vizPanelDataToPanel( const panel: Pick< Panel, - 'datasource' | 'targets' | 'maxDataPoints' | 'transformations' | 'cacheTimeout' | 'queryCachingTTL' + 'datasource' | 'targets' | 'maxDataPoints' | 'transformations' | 'cacheTimeout' | 'queryCachingTTL' | 'interval' > = {}; const queryRunner = getQueryRunnerFor(vizPanel); @@ -297,6 +297,9 @@ function vizPanelDataToPanel( if (queryRunner.state.queryCachingTTL) { panel.queryCachingTTL = queryRunner.state.queryCachingTTL; } + if (queryRunner.state.minInterval) { + panel.interval = queryRunner.state.minInterval; + } } if (dataProvider instanceof SceneDataTransformer) { diff --git a/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts b/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts index e9754ebf206..758d60be473 100644 --- a/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts +++ b/public/app/features/dashboard-scene/utils/createPanelDataProvider.ts @@ -24,6 +24,7 @@ export function createPanelDataProvider(panel: PanelModel): SceneDataProvider | maxDataPointsFromWidth: true, cacheTimeout: panel.cacheTimeout, queryCachingTTL: panel.queryCachingTTL, + minInterval: panel.interval ?? undefined, dataLayerFilter: { panelId: panel.id, }, From 67968df70e76ba7c78971018f0e59448eabe0709 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 29 Apr 2024 09:49:46 +0200 Subject: [PATCH 145/222] DashboardDataSourceBehaviour: Handle loading library panel (#86980) * DashboardDataSourceBehaviour: Handle loading library panel * Remove timeout * FIx test --- .../DashboardDatasourceBehaviour.test.tsx | 82 +++++++++++++++++++ .../scene/DashboardDatasourceBehaviour.tsx | 39 +++++++-- .../dashboard-scene/scene/LibraryVizPanel.tsx | 2 +- 3 files changed, 116 insertions(+), 7 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx index 453b3e7983f..86814f13b4b 100644 --- a/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.test.tsx @@ -21,6 +21,7 @@ import { activateFullSceneTree } from '../utils/test-utils'; import { DashboardDatasourceBehaviour } from './DashboardDatasourceBehaviour'; import { DashboardGridItem } from './DashboardGridItem'; import { DashboardScene } from './DashboardScene'; +import { LibraryVizPanel } from './LibraryVizPanel'; const grafanaDs = { id: 1, @@ -487,6 +488,87 @@ describe('DashboardDatasourceBehaviour', () => { } }); }); + + describe('Library panels', () => { + it('should wait for library panel to be loaded', async () => { + const sourcePanel = new LibraryVizPanel({ + name: 'My Library Panel', + title: 'Panel title', + uid: 'fdcvggvfy2qdca', + panelKey: 'lib-panel', + panel: new VizPanel({ + key: 'panel-1', + title: 'Panel A', + pluginId: 'table', + }), + }); + + // query references inexistent panel + const dashboardDSPanel = new VizPanel({ + title: 'Panel B', + pluginId: 'table', + key: 'panel-2', + $data: new SceneQueryRunner({ + datasource: { uid: SHARED_DASHBOARD_QUERY }, + queries: [{ refId: 'A', panelId: 1 }], + $behaviors: [new DashboardDatasourceBehaviour({})], + }), + }); + + const scene = new DashboardScene({ + title: 'hello', + uid: 'dash-1', + meta: { + canEdit: true, + }, + body: new SceneGridLayout({ + children: [ + new DashboardGridItem({ + key: 'griditem-1', + x: 0, + y: 0, + width: 10, + height: 12, + body: sourcePanel, + }), + new DashboardGridItem({ + key: 'griditem-2', + x: 0, + y: 0, + width: 10, + height: 12, + body: dashboardDSPanel, + }), + ], + }), + }); + + activateFullSceneTree(scene); + + // spy on runQueries + const spy = jest.spyOn(dashboardDSPanel.state.$data as SceneQueryRunner, 'runQueries'); + + await new Promise((r) => setTimeout(r, 1)); + + expect(spy).not.toHaveBeenCalled(); + + // Simulate library panel being loaded + sourcePanel.setState({ + isLoaded: true, + panel: new VizPanel({ + title: 'Panel A', + pluginId: 'table', + key: 'panel-1', + $data: new SceneQueryRunner({ + datasource: { uid: 'grafana' }, + queries: [{ refId: 'A', queryType: 'randomWalk' }], + }), + }), + }); + + expect(spy).toHaveBeenCalledTimes(1); + }); + }); }); async function buildTestScene() { diff --git a/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.tsx b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.tsx index d39903a6ca0..26fb6f0c8bc 100644 --- a/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.tsx @@ -1,9 +1,12 @@ +import { Unsubscribable } from 'rxjs'; + import { SceneObjectBase, SceneObjectState, SceneQueryRunner, VizPanel } from '@grafana/scenes'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; import { findVizPanelByKey, getDashboardSceneFor, getQueryRunnerFor, getVizPanelKeyForPanelId } from '../utils/utils'; import { DashboardScene } from './DashboardScene'; +import { LibraryVizPanel, LibraryVizPanelState } from './LibraryVizPanel'; interface DashboardDatasourceBehaviourState extends SceneObjectState {} @@ -18,6 +21,7 @@ export class DashboardDatasourceBehaviour extends SceneObjectBase { + this.handleLibPanelStateUpdates(n, p, queryRunner); + }); + } + } else { + if (this.prevRequestId && this.prevRequestId !== sourcePanelQueryRunner.state.data?.request?.requestId) { + queryRunner.runQueries(); + } } return () => { - this.prevRequestId = sourcePanelQueryRunner.state.data?.request?.requestId; + this.prevRequestId = sourcePanelQueryRunner?.state.data?.request?.requestId; + if (libraryPanelSub) { + libraryPanelSub.unsubscribe(); + } }; } + + private handleLibPanelStateUpdates(n: LibraryVizPanelState, p: LibraryVizPanelState, queryRunner: SceneQueryRunner) { + if (n.panel && n.panel !== p.panel) { + const libPanelQueryRunner = getQueryRunnerFor(n.panel); + + if (!(libPanelQueryRunner instanceof SceneQueryRunner)) { + throw new Error('Could not find SceneQueryRunner for panel'); + } + + queryRunner.runQueries(); + } + } } diff --git a/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx b/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx index 76e70db37cc..266eae58326 100644 --- a/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx +++ b/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx @@ -19,7 +19,7 @@ import { VizPanelLinks, VizPanelLinksMenu } from './PanelLinks'; import { panelLinksBehavior, panelMenuBehavior } from './PanelMenuBehavior'; import { PanelNotices } from './PanelNotices'; -interface LibraryVizPanelState extends SceneObjectState { +export interface LibraryVizPanelState extends SceneObjectState { // Library panels use title from dashboard JSON's panel model, not from library panel definition, hence we pass it. title: string; uid: string; From 500558bb72d9d6c8e95c934dfbffd699872a4814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Mon, 29 Apr 2024 09:55:09 +0200 Subject: [PATCH 146/222] mysql decouple frontend part (#86308) * mysql decouple frontend part * eslint "fix" * package version update --- .../app/features/plugins/built_in_plugins.ts | 3 -- .../app/plugins/datasource/mysql/CHANGELOG.md | 1 + .../app/plugins/datasource/mysql/package.json | 41 +++++++++++++++++++ .../plugins/datasource/mysql/tsconfig.json | 4 ++ .../datasource/mysql/webpack.config.ts | 4 ++ yarn.lock | 33 ++++++++++++++- 6 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 public/app/plugins/datasource/mysql/CHANGELOG.md create mode 100644 public/app/plugins/datasource/mysql/package.json create mode 100644 public/app/plugins/datasource/mysql/tsconfig.json create mode 100644 public/app/plugins/datasource/mysql/webpack.config.ts diff --git a/public/app/features/plugins/built_in_plugins.ts b/public/app/features/plugins/built_in_plugins.ts index 03dd4d2a603..5a0bf8ab763 100644 --- a/public/app/features/plugins/built_in_plugins.ts +++ b/public/app/features/plugins/built_in_plugins.ts @@ -15,8 +15,6 @@ const influxdbPlugin = async () => const lokiPlugin = async () => await import(/* webpackChunkName: "lokiPlugin" */ 'app/plugins/datasource/loki/module'); const mixedPlugin = async () => await import(/* webpackChunkName: "mixedPlugin" */ 'app/plugins/datasource/mixed/module'); -const mysqlPlugin = async () => - await import(/* webpackChunkName: "mysqlPlugin" */ 'app/plugins/datasource/mysql/module'); const postgresPlugin = async () => await import(/* webpackChunkName: "postgresPlugin" */ 'app/plugins/datasource/grafana-postgresql-datasource/module'); const prometheusPlugin = async () => @@ -83,7 +81,6 @@ const builtInPlugins: Record Promise Date: Mon, 29 Apr 2024 10:56:06 +0300 Subject: [PATCH 147/222] Home dashboard test (#86961) home dashboard test --- .../pages/DashboardScenePage.test.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx index 3b8e8699882..487397536d7 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx +++ b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx @@ -1,4 +1,4 @@ -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { cloneDeep } from 'lodash'; import React from 'react'; @@ -253,6 +253,18 @@ describe('DashboardScenePage', () => { const editMenuItem = await screen.findAllByText('Edit'); expect(editMenuItem).toHaveLength(1); }); + + describe('home page', () => { + it('should not show controls', async () => { + getDashboardScenePageStateManager().clearDashboardCache(); + loadDashboardMock.mockClear(); + loadDashboardMock.mockResolvedValue({ dashboard: { panels: [] }, meta: {} }); + + setup(); + + await waitFor(() => expect(screen.queryByText('Refresh')).not.toBeInTheDocument()); + }); + }); }); interface VizOptions { From 01f83015047d0026f3d7083430e930ad5d6e6c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 29 Apr 2024 10:28:41 +0200 Subject: [PATCH 148/222] DashboardScene: Fixes issue with dashboard links and variables (#86910) * DashboardScene: Fixes issue with dashboard links and variables * Update --- .../dashboard-scene/scene/DashboardControls.tsx | 15 +++++++++++++++ .../SubMenu/DashboardLinksDashboard.tsx | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx index 693e6b9ba46..e62cd864efe 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx @@ -10,6 +10,7 @@ import { SceneTimePicker, SceneRefreshPicker, SceneDebugger, + VariableDependencyConfig, } from '@grafana/scenes'; import { Box, Stack, useStyles2 } from '@grafana/ui'; @@ -27,6 +28,10 @@ interface DashboardControlsState extends SceneObjectState { export class DashboardControls extends SceneObjectBase { static Component = DashboardControlsRenderer; + protected _variableDependency = new VariableDependencyConfig(this, { + onAnyVariableChanged: this._onAnyVariableChanged.bind(this), + }); + public constructor(state: Partial) { super({ variableControls: [], @@ -35,6 +40,16 @@ export class DashboardControls extends SceneObjectBase { ...state, }); } + + /** + * Links can include all variables so we need to re-render when any change + */ + private _onAnyVariableChanged(): void { + const dashboard = getDashboardSceneFor(this); + if (dashboard.state.links?.length > 0) { + this.forceRender(); + } + } } function DashboardControlsRenderer({ model }: SceneComponentProps) { diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx index fe77b4870e4..5d02274ad8f 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx @@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css'; import React from 'react'; import { useAsync } from 'react-use'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, ScopedVars } from '@grafana/data'; import { sanitize, sanitizeUrl } from '@grafana/data/src/text/sanitize'; import { selectors } from '@grafana/e2e-selectors'; import { DashboardLink } from '@grafana/schema'; @@ -17,6 +17,7 @@ interface Props { link: DashboardLink; linkInfo: { title: string; href: string }; dashboardUID: string; + scopedVars?: ScopedVars; } interface DashboardLinksMenuProps { From a4bb4c84002374f24cfc44efa42304e131353d89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 29 Apr 2024 10:53:57 +0200 Subject: [PATCH 149/222] DashboardScene: Fixes issues with relative time range in panel edit (#86862) * DashboardScene: Fixes deleting dirty dashboard * Update * Progress * Update * Update * Update * Update * Update * Update * update * Update --- .../PanelDataPane/PanelDataQueriesTab.tsx | 12 +++++- .../panel-edit/PanelEditor.tsx | 2 +- .../panel-edit/VizPanelManager.tsx | 37 ++++++++----------- .../dashboard-scene/scene/PanelTimeRange.tsx | 4 +- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx index 324a7f9e232..a8bde9e9c93 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx @@ -42,11 +42,19 @@ export class PanelDataQueriesTab extends SceneObjectBase { return QueriesTab({ ...props, model: this }); }; this._panelManager = panelManager; + this.addActivationHandler(this.onActivate.bind(this)); + } + + private onActivate() { + // This is to preserve SceneQueryRunner stays alive when switching between visualizations and table view + const deactivate = this._panelManager.queryRunner.activate(); + return () => deactivate(); } buildQueryOptions(): QueryGroupOptions { @@ -179,7 +187,7 @@ export class PanelDataQueriesTab extends SceneObjectBase) { const { datasource, dsSettings } = model.panelManager.useState(); - const { data } = model.panelManager.queryRunner.useState(); + const { data, queries } = model.panelManager.queryRunner.useState(); if (!datasource || !dsSettings || !data) { return null; @@ -201,7 +209,7 @@ function PanelDataQueriesTabRendered({ model }: SceneComponentProps { } panelRepeater.setState({ - body: panelManager.getPanelCloneWithData(), + body: panelManager.state.panel.clone(), repeatDirection: panelManager.state.repeatDirection, variableName: panelManager.state.repeat, maxPerRow: panelManager.state.maxPerRow, diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx index a58b3860262..ed43cefb919 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx @@ -22,7 +22,6 @@ import { SceneObjectState, SceneQueryRunner, VizPanel, - sceneGraph, sceneUtils, } from '@grafana/scenes'; import { DataQuery, DataTransformerConfig, Panel } from '@grafana/schema'; @@ -88,8 +87,7 @@ export class VizPanelManager extends SceneObjectBase { repeatOptions = { repeat, repeatDirection, maxPerRow }; return new VizPanelManager({ - panel: sourcePanel.clone({ $data: undefined }), - $data: sourcePanel.state.$data?.clone(), + panel: sourcePanel.clone(), sourcePanel: sourcePanel.getRef(), ...repeatOptions, }); @@ -100,7 +98,7 @@ export class VizPanelManager extends SceneObjectBase { } private async loadDataSource() { - const dataObj = this.state.$data; + const dataObj = this.state.panel.state.$data; if (!dataObj) { return; @@ -207,14 +205,14 @@ export class VizPanelManager extends SceneObjectBase { }); // When changing from non-data to data panel, we need to add a new data provider - if (!this.state.$data && !config.panels[pluginId].skipDataQuery) { + if (!this.state.panel.state.$data && !config.panels[pluginId].skipDataQuery) { let ds = getLastUsedDatasourceFromStorage(getDashboardSceneFor(this).state.uid!)?.datasourceUid; if (!ds) { ds = config.defaultDatasource; } - this.setState({ + newPanel.setState({ $data: new SceneDataTransformer({ $data: new SceneQueryRunner({ datasource: { @@ -281,7 +279,7 @@ export class VizPanelManager extends SceneObjectBase { public changeQueryOptions(options: QueryGroupOptions) { const panelObj = this.state.panel; const dataObj = this.queryRunner; - let timeRangeObj = sceneGraph.getTimeRange(panelObj); + const timeRangeObj = panelObj.state.$timeRange; const dataObjStateUpdate: Partial = {}; const timeRangeObjStateUpdate: Partial = {}; @@ -348,7 +346,7 @@ export class VizPanelManager extends SceneObjectBase { get queryRunner(): SceneQueryRunner { // Panel data object is always SceneQueryRunner wrapped in a SceneDataTransformer - const runner = getQueryRunnerFor(this); + const runner = getQueryRunnerFor(this.state.panel); if (!runner) { throw new Error('Query runner not found'); @@ -358,7 +356,7 @@ export class VizPanelManager extends SceneObjectBase { } get dataTransformer(): SceneDataTransformer { - const provider = this.state.$data; + const provider = this.state.panel.state.$data; if (!provider || !(provider instanceof SceneDataTransformer)) { throw new Error('Could not find SceneDataTransformer for panel'); } @@ -376,6 +374,9 @@ export class VizPanelManager extends SceneObjectBase { .setTitle('') .setOption('showTypeIcons', true) .setOption('showHeader', true) + // Here we are breaking a scene rule and changing the parent of the main panel data provider + // But we need to share this same instance as the queries tab is subscribing to it + .setData(this.dataTransformer) .build(), }); } @@ -415,23 +416,21 @@ export class VizPanelManager extends SceneObjectBase { if (sourcePanel.parent instanceof DashboardGridItem) { sourcePanel.parent.setState({ ...repeatUpdate, - body: this.state.panel.clone({ - $data: this.state.$data?.clone(), - }), + body: this.state.panel.clone(), }); } if (sourcePanel.parent instanceof LibraryVizPanel) { if (sourcePanel.parent.parent instanceof DashboardGridItem) { const newLibPanel = sourcePanel.parent.clone({ - panel: this.state.panel.clone({ - $data: this.state.$data?.clone(), - }), + panel: this.state.panel.clone(), }); + sourcePanel.parent.parent.setState({ body: newLibPanel, ...repeatUpdate, }); + updateLibraryVizPanel(newLibPanel!).then((p) => { if (sourcePanel.parent instanceof LibraryVizPanel) { newLibPanel.setPanelFromLibPanel(p); @@ -455,18 +454,12 @@ export class VizPanelManager extends SceneObjectBase { } const parentClone = gridItem.clone({ - body: this.state.panel.clone({ - $data: this.state.$data?.clone(), - }), + body: this.state.panel.clone(), }); return gridItemToPanel(parentClone); } - public getPanelCloneWithData(): VizPanel { - return this.state.panel.clone({ $data: this.state.$data?.clone() }); - } - public setPanelTitle(newTitle: string) { this.state.panel.setState({ title: newTitle, hoverHeader: newTitle === '' }); } diff --git a/public/app/features/dashboard-scene/scene/PanelTimeRange.tsx b/public/app/features/dashboard-scene/scene/PanelTimeRange.tsx index 8a217c49fdb..cd4d0637c65 100644 --- a/public/app/features/dashboard-scene/scene/PanelTimeRange.tsx +++ b/public/app/features/dashboard-scene/scene/PanelTimeRange.tsx @@ -39,8 +39,8 @@ export class PanelTimeRange extends SceneTimeRangeTransformerBase { // Listen to own changes and update time info when required if (n.timeFrom !== p.timeFrom || n.timeShift !== p.timeShift) { - const { timeInfo } = this.getTimeOverride(this.getAncestorTimeRange().state.value); - this.setState({ timeInfo }); + const { timeInfo, timeRange } = this.getTimeOverride(this.getAncestorTimeRange().state.value); + this.setState({ timeInfo, value: timeRange }); } }) ); From e89f6daedad60b8e3341c02cd33847bf3860ddab Mon Sep 17 00:00:00 2001 From: Levente Balogh Date: Mon, 29 Apr 2024 11:04:03 +0200 Subject: [PATCH 150/222] Plugins: Add an auto-generated part to the `plugin.json` schema (#86520) feat: update the plugin.json schema --- .../developers/plugins/plugin.schema.json | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/sources/developers/plugins/plugin.schema.json b/docs/sources/developers/plugins/plugin.schema.json index 4947f96e0f2..09ab8106176 100644 --- a/docs/sources/developers/plugins/plugin.schema.json +++ b/docs/sources/developers/plugins/plugin.schema.json @@ -555,6 +555,35 @@ } } } + }, + "generated": { + "type": "object", + "description": "Auto-generated metadata for the plugin (usually automatically extracted from the source code during build time).", + "properties": { + "extensions": { + "type": "array", + "description": "List of the extensions that the plugin registers.", + "items": { + "type": "object", + "properties": { + "extensionPointId": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["link", "component"] + } + }, + "required": ["extensionPointId", "title", "description", "type"] + } + } + } } } } From ccd2bff8b0179e4bfbe20c927de410edd29ffc08 Mon Sep 17 00:00:00 2001 From: Dai Nguyen <88277570+ej25a@users.noreply.github.com> Date: Mon, 29 Apr 2024 04:24:49 -0500 Subject: [PATCH 151/222] Docs: Create the Azure AD application section has outdated information. (#71498) Update index.md --- .../configure-authentication/azuread/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md index 8d0b79451e7..9dbda4334f9 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md @@ -49,7 +49,7 @@ To enable the Azure AD OAuth2, register your application with Azure AD. 1. Click **Certificates & secrets**, then add a new entry under **Client secrets** with the following configuration. - Description: Grafana OAuth - - Expires: Never + - Expires: Select an expiration period 1. Click **Add** then copy the key value. This is the OAuth client secret. From 7fab894e9b0a84c01c1a11db3aa4f44464c417c0 Mon Sep 17 00:00:00 2001 From: Thomas Wikman Date: Mon, 29 Apr 2024 11:46:44 +0200 Subject: [PATCH 152/222] DateTimePicker: Alternate timezones now behave correctly (#86750) * Add failing tests for timezone handling * Fix `DateTimePicker.tsx` timezone handling - Resolves `onBlur` issue - Resolve Calendar and TimeOfDay issues - Update test to cover different timezone * Handle `console.warn` in test * Handle `console.warn` in test #2 * Better handling of invalid date When parsing date string with `dateTime`, adding a second `formatInput` aids in both parsing the actual string and avoid `console.warn` when `moment` reverts to be using `Date`. * add more test cases * Ash/proposed changes (#86854) * simplify * only need this change * formatting * const > let * add test to ensure calendar is always showing the matching day * separate state * undo story changes * update util function comments * fix for selecting date in the calendar --------- Co-authored-by: Ashley Harrison --- .../src/datetime/moment_wrapper.ts | 2 +- .../DateTimePicker/DateTimePicker.test.tsx | 207 +++++++++++++++--- .../DateTimePicker/DateTimePicker.tsx | 57 +++-- .../TimeRangePicker/CalendarBody.tsx | 39 +--- .../utils/adjustDateForReactCalendar.ts | 28 +++ 5 files changed, 254 insertions(+), 79 deletions(-) create mode 100644 packages/grafana-ui/src/components/DateTimePickers/utils/adjustDateForReactCalendar.ts diff --git a/packages/grafana-data/src/datetime/moment_wrapper.ts b/packages/grafana-data/src/datetime/moment_wrapper.ts index 0b97de1a3bf..6b60bb524bd 100644 --- a/packages/grafana-data/src/datetime/moment_wrapper.ts +++ b/packages/grafana-data/src/datetime/moment_wrapper.ts @@ -53,7 +53,7 @@ export interface DateTimeDuration { export interface DateTime extends Object { add: (amount?: DateTimeInput, unit?: DurationUnit) => DateTime; - set: (unit: DurationUnit, amount: DateTimeInput) => void; + set: (unit: DurationUnit | 'date', amount: DateTimeInput) => void; diff: (amount: DateTimeInput, unit?: DurationUnit, truncate?: boolean) => number; endOf: (unitOfTime: DurationUnit) => DateTime; format: (formatInput?: FormatInput) => string; diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx index 1f4bed87550..5258ee22ad0 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.test.tsx @@ -2,15 +2,23 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { dateTime } from '@grafana/data'; +import { dateTime, dateTimeAsMoment, dateTimeForTimeZone, getTimeZone, setTimeZoneResolver } from '@grafana/data'; import { Components } from '@grafana/e2e-selectors'; import { DateTimePicker, Props } from './DateTimePicker'; +// An assortment of timezones that we will test the behavior of the DateTimePicker with different timezones +const TEST_TIMEZONES = ['browser', 'Europe/Stockholm', 'America/Indiana/Marengo']; + +const defaultTimeZone = getTimeZone(); +afterAll(() => { + return setTimeZoneResolver(() => defaultTimeZone); +}); + const renderDatetimePicker = (props?: Props) => { const combinedProps = Object.assign( { - date: dateTime('2021-05-05 12:00:00'), + date: dateTimeForTimeZone(getTimeZone(), '2021-05-05 12:00:00'), onChange: () => {}, }, props @@ -26,12 +34,22 @@ describe('Date time picker', () => { expect(screen.queryByTestId('date-time-picker')).toBeInTheDocument(); }); - it('input should have a value', () => { + it.each(TEST_TIMEZONES)('input should have a value (timezone: %s)', (timeZone) => { + setTimeZoneResolver(() => timeZone); renderDatetimePicker(); - expect(screen.queryByDisplayValue('2021-05-05 12:00:00')).toBeInTheDocument(); + const dateTimeInput = screen.getByTestId(Components.DateTimePicker.input); + expect(dateTimeInput).toHaveDisplayValue('2021-05-05 12:00:00'); }); - it('should update date onblur', async () => { + it.each(TEST_TIMEZONES)('should render (timezone %s)', (timeZone) => { + setTimeZoneResolver(() => timeZone); + renderDatetimePicker(); + const dateTimeInput = screen.getByTestId(Components.DateTimePicker.input); + expect(dateTimeInput).toHaveDisplayValue('2021-05-05 12:00:00'); + }); + + it.each(TEST_TIMEZONES)('should update date onblur (timezone: %)', async (timeZone) => { + setTimeZoneResolver(() => timeZone); const onChangeInput = jest.fn(); render(); const dateTimeInput = screen.getByTestId(Components.DateTimePicker.input); @@ -42,7 +60,8 @@ describe('Date time picker', () => { expect(onChangeInput).toHaveBeenCalled(); }); - it('should not update onblur if invalid date', async () => { + it.each(TEST_TIMEZONES)('should not update onblur if invalid date (timezone: %s)', async (timeZone) => { + setTimeZoneResolver(() => timeZone); const onChangeInput = jest.fn(); render(); const dateTimeInput = screen.getByTestId(Components.DateTimePicker.input); @@ -53,31 +72,167 @@ describe('Date time picker', () => { expect(onChangeInput).not.toHaveBeenCalled(); }); - it('should be able to select values in TimeOfDayPicker without blurring the element', async () => { - renderDatetimePicker(); + it.each(TEST_TIMEZONES)( + 'should not change the day at times near the day boundary (timezone: %s)', + async (timeZone) => { + setTimeZoneResolver(() => timeZone); + const onChangeInput = jest.fn(); + render(); - // open the calendar + time picker - await userEvent.click(screen.getByLabelText('Time picker')); + // Click the calendar button + await userEvent.click(screen.getByRole('button', { name: 'Time picker' })); - // open the time of day overlay - await userEvent.click(screen.getAllByRole('textbox')[1]); + // Check the active day is the 5th + expect(screen.getByRole('button', { name: 'May 5, 2021' })).toHaveClass('react-calendar__tile--active'); - // check the hour element is visible - const hourElement = screen.getAllByRole('button', { - name: '00', - })[0]; - expect(hourElement).toBeVisible(); + // open the time of day overlay + await userEvent.click(screen.getAllByRole('textbox')[1]); - // select the hour value and check it's still visible - await userEvent.click(hourElement); - expect(hourElement).toBeVisible(); + // change the hour + await userEvent.click( + screen.getAllByRole('button', { + name: '00', + })[0] + ); - // click outside the overlay and check the hour element is no longer visible + // Check the active day is the 5th + expect(screen.getByRole('button', { name: 'May 5, 2021' })).toHaveClass('react-calendar__tile--active'); + + // change the hour + await userEvent.click( + screen.getAllByRole('button', { + name: '23', + })[0] + ); + + // Check the active day is the 5th + expect(screen.getByRole('button', { name: 'May 5, 2021' })).toHaveClass('react-calendar__tile--active'); + } + ); + + it.each(TEST_TIMEZONES)( + 'should not reset the time when selecting a different day (timezone: %s)', + async (timeZone) => { + setTimeZoneResolver(() => timeZone); + const onChangeInput = jest.fn(); + render(); + + // Click the calendar button + await userEvent.click(screen.getByRole('button', { name: 'Time picker' })); + + // Select a different day in the calendar + await userEvent.click(screen.getByRole('button', { name: 'May 15, 2021' })); + + const timeInput = screen.getAllByRole('textbox')[1]; + expect(timeInput).toHaveClass('rc-time-picker-input'); + expect(timeInput).not.toHaveDisplayValue('00:00:00'); + } + ); + + it.each(TEST_TIMEZONES)( + 'should always show the correct matching day in the calendar (timezone: %s)', + async (timeZone) => { + setTimeZoneResolver(() => timeZone); + const onChangeInput = jest.fn(); + render(); + + const dateTimeInputValue = screen.getByTestId(Components.DateTimePicker.input).getAttribute('value')!; + + // takes the string from the input + // depending on the timezone, this will look something like 2024-04-05 19:59:41 + // parses out the day value and strips the leading 0 + const day = parseInt(dateTimeInputValue.split(' ')[0].split('-')[2], 10); + + // Click the calendar button + await userEvent.click(screen.getByRole('button', { name: 'Time picker' })); + + // Check the active day matches the input + expect(screen.getByRole('button', { name: `May ${day}, 2021` })).toHaveClass('react-calendar__tile--active'); + } + ); + + it.each(TEST_TIMEZONES)( + 'should always show the correct matching day when selecting a date in the calendar (timezone: %s)', + async (timeZone) => { + setTimeZoneResolver(() => timeZone); + const onChangeInput = jest.fn(); + render(); + + // Click the calendar button + await userEvent.click(screen.getByRole('button', { name: 'Time picker' })); + + // Select a new day + const day = 8; + await userEvent.click(screen.getByRole('button', { name: `May ${day}, 2021` })); + await userEvent.click(screen.getByRole('button', { name: 'Apply' })); + + const onChangeInputArg = onChangeInput.mock.calls[0][0]; + + expect(dateTimeAsMoment(dateTimeForTimeZone(timeZone, onChangeInputArg)).date()).toBe(day); + } + ); + + it.each(TEST_TIMEZONES)('should not alter a UTC time when blurring (timezone: %s)', async (timeZone) => { + setTimeZoneResolver(() => timeZone); + const onChangeInput = jest.fn(); + + // render with a UTC value + const { rerender } = render( + + ); + + const inputValue = screen.getByTestId(Components.DateTimePicker.input).getAttribute('value')!; + + // blur the input to trigger an onChange + await userEvent.click(screen.getByTestId(Components.DateTimePicker.input)); await userEvent.click(document.body); - expect( - screen.queryByRole('button', { - name: '00', - }) - ).not.toBeInTheDocument(); + + const onChangeValue = onChangeInput.mock.calls[0][0]; + expect(onChangeInput).toHaveBeenCalledWith(onChangeValue); + + // now rerender with the "changed" value + rerender(); + + // expect the input to show the same value + expect(screen.getByTestId(Components.DateTimePicker.input)).toHaveDisplayValue(inputValue); + + // blur the input to trigger an onChange + await userEvent.click(screen.getByTestId(Components.DateTimePicker.input)); + await userEvent.click(document.body); + + // expect the onChange to be called with the same value + expect(onChangeInput).toHaveBeenCalledWith(onChangeValue); }); + + it.each(TEST_TIMEZONES)( + 'should be able to select values in TimeOfDayPicker without blurring the element (timezone: %s)', + async (timeZone) => { + setTimeZoneResolver(() => timeZone); + renderDatetimePicker(); + + // open the calendar + time picker + await userEvent.click(screen.getByLabelText('Time picker')); + + // open the time of day overlay + await userEvent.click(screen.getAllByRole('textbox')[1]); + + // check the hour element is visible + const hourElement = screen.getAllByRole('button', { + name: '00', + })[0]; + expect(hourElement).toBeVisible(); + + // select the hour value and check it's still visible + await userEvent.click(hourElement); + expect(hourElement).toBeVisible(); + + // click outside the overlay and check the hour element is no longer visible + await userEvent.click(document.body); + expect( + screen.queryByRole('button', { + name: '00', + }) + ).not.toBeInTheDocument(); + } + ); }); diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx index 3df2c3b7565..60d8ba4bdb2 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx @@ -7,7 +7,15 @@ import React, { FormEvent, ReactNode, useCallback, useEffect, useRef, useState } import Calendar from 'react-calendar'; import { useMedia } from 'react-use'; -import { dateTimeFormat, DateTime, dateTime, GrafanaTheme2, isDateTime } from '@grafana/data'; +import { + dateTimeFormat, + DateTime, + dateTime, + GrafanaTheme2, + isDateTime, + dateTimeForTimeZone, + getTimeZone, +} from '@grafana/data'; import { Components } from '@grafana/e2e-selectors'; import { useStyles2, useTheme2 } from '../../../themes'; @@ -21,6 +29,7 @@ import { Portal } from '../../Portal/Portal'; import { TimeOfDayPicker, POPUP_CLASS_NAME } from '../TimeOfDayPicker'; import { getBodyStyles } from '../TimeRangePicker/CalendarBody'; import { isValid } from '../utils'; +import { adjustDateForReactCalendar } from '../utils/adjustDateForReactCalendar'; export interface Props { /** Input date for the component */ @@ -227,7 +236,7 @@ const DateTimeInput = React.forwardRef( const onBlur = useCallback(() => { if (!internalDate.invalid) { - const date = dateTime(internalDate.value); + const date = dateTimeForTimeZone(getTimeZone(), internalDate.value); onChange(date); } }, [internalDate, onChange]); @@ -276,9 +285,18 @@ const DateTimeCalendar = React.forwardRef ) => { const calendarStyles = useStyles2(getBodyStyles); const styles = useStyles2(getStyles); - const [internalDate, setInternalDate] = useState(() => { + + // need to keep these 2 separate in state since react-calendar doesn't support different timezones + const [timeOfDayDateTime, setTimeOfDayDateTime] = useState(() => { if (date && date.isValid()) { - return date.toDate(); + return dateTimeForTimeZone(getTimeZone(), date); + } + + return dateTimeForTimeZone(getTimeZone(), new Date()); + }); + const [reactCalendarDate, setReactCalendarDate] = useState(() => { + if (date && date.isValid()) { + return adjustDateForReactCalendar(date.toDate(), getTimeZone()); } return new Date(); @@ -286,28 +304,33 @@ const DateTimeCalendar = React.forwardRef const onChangeDate = useCallback['onChange']>>((date) => { if (date && !Array.isArray(date)) { - setInternalDate((prevState) => { - // If we don't use time from prevState - // the time will be reset to 00:00:00 - date.setHours(prevState.getHours()); - date.setMinutes(prevState.getMinutes()); - date.setSeconds(prevState.getSeconds()); - - return date; - }); + setReactCalendarDate(date); } }, []); const onChangeTime = useCallback((date: DateTime) => { - setInternalDate(date.toDate()); + setTimeOfDayDateTime(date); }, []); + // here we need to stitch the 2 date objects back together + const handleApply = () => { + // we take the date that's set by TimeOfDayPicker + const newDate = dateTime(timeOfDayDateTime); + + // and apply the date/month/year set by react-calendar + newDate.set('date', reactCalendarDate.getDate()); + newDate.set('month', reactCalendarDate.getMonth()); + newDate.set('year', reactCalendarDate.getFullYear()); + + onChange(newDate); + }; + return (
} nextAriaLabel="Next month" prevLabel={} @@ -323,14 +346,14 @@ const DateTimeCalendar = React.forwardRef
-
- + @@ -556,7 +556,7 @@ export class UnthemedLokiLabelBrowser extends React.Component Clear - +
); diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx index 5db5313f985..2d45f8c96eb 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { useStyles2, HorizontalGroup, IconButton, Tooltip, Icon } from '@grafana/ui'; +import { useStyles2, IconButton, Tooltip, Icon, Stack } from '@grafana/ui'; import { testIds } from '../../components/LokiQueryEditor'; import { LokiQueryField } from '../../components/LokiQueryField'; @@ -49,7 +49,7 @@ export function LokiQueryCodeEditor({ {lokiFormatQuery && (
- + - +
)} From 5830d6761d745740db5c81c9c2be58bcb7b94601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Jamr=C3=B3z?= Date: Mon, 29 Apr 2024 14:33:00 +0200 Subject: [PATCH 159/222] ifrost/fix-link (#86965) * Fix link label * Switch to lowercase --- public/app/features/explore/ShortLinkButtonMenu.tsx | 4 ++-- public/locales/en-US/grafana.json | 4 ++-- public/locales/pseudo-LOCALE/grafana.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/app/features/explore/ShortLinkButtonMenu.tsx b/public/app/features/explore/ShortLinkButtonMenu.tsx index 5c7bebcc00f..36451f39ead 100644 --- a/public/app/features/explore/ShortLinkButtonMenu.tsx +++ b/public/app/features/explore/ShortLinkButtonMenu.tsx @@ -83,7 +83,7 @@ export function ShortLinkButtonMenu() { { key: 'copy-short-link-abs-time', icon: 'clock-nine', - label: t('explore.toolbar.copy-shortened-link-abs-time', 'Copy Absolute Shortened URL'), + label: t('explore.toolbar.copy-shortened-link-abs-time', 'Copy absolute shortened URL'), shorten: true, getUrl: () => { return constructAbsoluteUrl(panes); @@ -93,7 +93,7 @@ export function ShortLinkButtonMenu() { { key: 'copy-link-abs-time', icon: 'clock-nine', - label: t('explore.toolbar.copy-link-abs-time', 'Copy Absolute Shortened URL'), + label: t('explore.toolbar.copy-link-abs-time', 'Copy absolute URL'), shorten: false, getUrl: () => { return constructAbsoluteUrl(panes); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index b14cc1fe96e..812dcbcf66a 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -584,11 +584,11 @@ "toolbar": { "aria-label": "Explore toolbar", "copy-link": "Copy URL", - "copy-link-abs-time": "Copy Absolute Shortened URL", + "copy-link-abs-time": "Copy absolute URL", "copy-links-absolute-category": "Time-sync URL links (share with time range intact)", "copy-links-normal-category": "Normal URL links", "copy-shortened-link": "Copy shortened URL", - "copy-shortened-link-abs-time": "Copy Absolute Shortened URL", + "copy-shortened-link-abs-time": "Copy absolute shortened URL", "copy-shortened-link-menu": "Open copy link options", "refresh-picker-cancel": "Cancel", "refresh-picker-run": "Run query", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 65abad5652f..6da92d43459 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -584,11 +584,11 @@ "toolbar": { "aria-label": "Ēχpľőřę ŧőőľþäř", "copy-link": "Cőpy ŮŖĿ", - "copy-link-abs-time": "Cőpy Åþşőľūŧę Ŝĥőřŧęʼnęđ ŮŖĿ", + "copy-link-abs-time": "Cőpy äþşőľūŧę ŮŖĿ", "copy-links-absolute-category": "Ŧįmę-şyʼnč ŮŖĿ ľįʼnĸş (şĥäřę ŵįŧĥ ŧįmę řäʼnģę įʼnŧäčŧ)", "copy-links-normal-category": "Ńőřmäľ ŮŖĿ ľįʼnĸş", "copy-shortened-link": "Cőpy şĥőřŧęʼnęđ ŮŖĿ", - "copy-shortened-link-abs-time": "Cőpy Åþşőľūŧę Ŝĥőřŧęʼnęđ ŮŖĿ", + "copy-shortened-link-abs-time": "Cőpy äþşőľūŧę şĥőřŧęʼnęđ ŮŖĿ", "copy-shortened-link-menu": "Øpęʼn čőpy ľįʼnĸ őpŧįőʼnş", "refresh-picker-cancel": "Cäʼnčęľ", "refresh-picker-run": "Ŗūʼn qūęřy", From b52e349639f562c206cf61f101ac9d85477ec574 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Mon, 29 Apr 2024 14:50:03 +0200 Subject: [PATCH 160/222] Explore and Correlations: Replace deprecated layout components (#86967) Replace deprecated layout components --- .betterer.results | 9 --------- .../correlations/Forms/CorrelationFormNavigation.tsx | 6 +++--- public/app/features/explore/CorrelationEditorModeBar.tsx | 6 +++--- .../explore/extensions/ConfirmNavigationModal.tsx | 6 +++--- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/.betterer.results b/.betterer.results index c38c7f43ca4..7badfe90429 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2379,9 +2379,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] ], - "public/app/features/correlations/Forms/CorrelationFormNavigation.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/correlations/components/Wizard/index.ts:5381": [ [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"] @@ -3011,9 +3008,6 @@ exports[`better eslint`] = { "public/app/features/explore/ContentOutline/ContentOutline.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/explore/CorrelationEditorModeBar.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/explore/Logs/LiveLogs.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], @@ -3445,9 +3439,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], - "public/app/features/explore/extensions/ConfirmNavigationModal.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/explore/hooks/useStateSync/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./external.utils\`)", "0"] ], diff --git a/public/app/features/correlations/Forms/CorrelationFormNavigation.tsx b/public/app/features/correlations/Forms/CorrelationFormNavigation.tsx index 831e3a61260..95d6d56fc24 100644 --- a/public/app/features/correlations/Forms/CorrelationFormNavigation.tsx +++ b/public/app/features/correlations/Forms/CorrelationFormNavigation.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Button, HorizontalGroup } from '@grafana/ui'; +import { Button, Stack } from '@grafana/ui'; import { Trans, t } from 'app/core/internationalization'; import { useWizardContext } from '../components/Wizard/wizardContext'; @@ -26,7 +26,7 @@ export const CorrelationFormNavigation = () => { ); return ( - + {currentPage > 0 ? ( - + ); diff --git a/public/app/features/explore/extensions/ConfirmNavigationModal.tsx b/public/app/features/explore/extensions/ConfirmNavigationModal.tsx index b9481582e80..e9c41612adf 100644 --- a/public/app/features/explore/extensions/ConfirmNavigationModal.tsx +++ b/public/app/features/explore/extensions/ConfirmNavigationModal.tsx @@ -2,7 +2,7 @@ import React, { ReactElement } from 'react'; import { locationUtil } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { Button, Modal, VerticalGroup } from '@grafana/ui'; +import { Button, Modal, Stack } from '@grafana/ui'; type Props = { onDismiss: () => void; @@ -20,9 +20,9 @@ export function ConfirmNavigationModal(props: Props): ReactElement { return ( - +

Do you want to proceed in the current tab or open a new tab?

-
+ )} - {!loading && } + {!isLoading && } Cancel diff --git a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx index 3d8c21c4b83..5ebd45fc748 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx @@ -1,14 +1,17 @@ import { css } from '@emotion/css'; -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { dateMath, GrafanaTheme2 } from '@grafana/data'; -import { CollapsableSection, Icon, Link, LinkButton, useStyles2, Stack } from '@grafana/ui'; +import { CollapsableSection, Icon, Link, LinkButton, useStyles2, Stack, Alert } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { alertSilencesApi } from 'app/features/alerting/unified/api/alertSilencesApi'; +import { alertmanagerApi } from 'app/features/alerting/unified/api/alertmanagerApi'; +import { featureDiscoveryApi } from 'app/features/alerting/unified/api/featureDiscoveryApi'; +import { SILENCES_POLL_INTERVAL_MS } from 'app/features/alerting/unified/utils/constants'; +import { getDatasourceAPIUid } from 'app/features/alerting/unified/utils/datasource'; import { AlertmanagerAlert, Silence, SilenceState } from 'app/plugins/datasource/alertmanager/types'; -import { useDispatch } from 'app/types'; import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbilities'; -import { expireSilenceAction } from '../../state/actions'; import { parseMatchers } from '../../utils/alertmanager'; import { getSilenceFiltersFromUrlParams, makeAMLink } from '../../utils/misc'; import { Authorize } from '../Authorize'; @@ -29,12 +32,30 @@ export interface SilenceTableItem extends Silence { type SilenceTableColumnProps = DynamicTableColumnProps; type SilenceTableItemProps = DynamicTableItemProps; interface Props { - silences: Silence[]; - alertManagerAlerts: AlertmanagerAlert[]; alertManagerSourceName: string; } -const SilencesTable = ({ silences, alertManagerAlerts, alertManagerSourceName }: Props) => { +const SilencesTable = ({ alertManagerSourceName }: Props) => { + const [getAmAlerts, { data: alertManagerAlerts, isLoading: amAlertsIsLoading }] = + alertmanagerApi.endpoints.getAlertmanagerAlerts.useLazyQuery({ pollingInterval: SILENCES_POLL_INTERVAL_MS }); + const [getSilences, { data: silences = [], isLoading, error }] = alertSilencesApi.endpoints.getSilences.useLazyQuery({ + pollingInterval: SILENCES_POLL_INTERVAL_MS, + }); + + const { currentData: amFeatures } = featureDiscoveryApi.useDiscoverAmFeaturesQuery( + { amSourceName: alertManagerSourceName ?? '' }, + { skip: !alertManagerSourceName } + ); + + const mimirLazyInitError = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (error as any)?.message?.includes('the Alertmanager is not configured') && amFeatures?.lazyConfigInit; + + useEffect(() => { + getSilences({ datasourceUid: getDatasourceAPIUid(alertManagerSourceName) }); + getAmAlerts({ amSourceName: alertManagerSourceName }); + }, [alertManagerSourceName, getAmAlerts, getSilences]); + const styles = useStyles2(getStyles); const [queryParams] = useQueryParams(); const filteredSilencesNotExpired = useFilteredSilences(silences, false); @@ -45,7 +66,7 @@ const SilencesTable = ({ silences, alertManagerAlerts, alertManagerSourceName }: const itemsNotExpired = useMemo((): SilenceTableItemProps[] => { const findSilencedAlerts = (id: string) => { - return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id)); + return (alertManagerAlerts || []).filter((alert) => alert.status.silencedBy.includes(id)); }; return filteredSilencesNotExpired.map((silence) => { const silencedAlerts = findSilencedAlerts(silence.id); @@ -58,7 +79,7 @@ const SilencesTable = ({ silences, alertManagerAlerts, alertManagerSourceName }: const itemsExpired = useMemo((): SilenceTableItemProps[] => { const findSilencedAlerts = (id: string) => { - return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id)); + return (alertManagerAlerts || []).filter((alert) => alert.status.silencedBy.includes(id)); }; return filteredSilencesExpired.map((silence) => { const silencedAlerts = findSilencedAlerts(silence.id); @@ -69,6 +90,29 @@ const SilencesTable = ({ silences, alertManagerAlerts, alertManagerSourceName }: }); }, [filteredSilencesExpired, alertManagerAlerts]); + if (isLoading || amAlertsIsLoading) { + return null; + } + + if (mimirLazyInitError) { + return ( + + Create a new contact point to create a configuration using the default values or contact your administrator to + set up the Alertmanager. + + ); + } + + if (error) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const errMessage = (error as any)?.message || 'Unknown error.'; + return ( + + {errMessage} + + ); + } + return (
{!!silences.length && ( @@ -169,43 +213,43 @@ const useFilteredSilences = (silences: Silence[], expired = false) => { }; const getStyles = (theme: GrafanaTheme2) => ({ - topButtonContainer: css` - display: flex; - flex-direction: row; - justify-content: flex-end; - `, - addNewSilence: css` - margin: ${theme.spacing(2, 0)}; - `, - callout: css` - background-color: ${theme.colors.background.secondary}; - border-top: 3px solid ${theme.colors.info.border}; - border-radius: ${theme.shape.radius.default}; - height: 62px; - display: flex; - flex-direction: row; - align-items: center; + topButtonContainer: css({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-end', + }), + addNewSilence: css({ + margin: theme.spacing(2, 0), + }), + callout: css({ + backgroundColor: theme.colors.background.secondary, + borderTop: `3px solid ${theme.colors.info.border}`, + borderRadius: theme.shape.radius.default, + height: '62px', + display: 'flex', + flexDirection: 'row', + alignItems: 'center', - & > * { - margin-left: ${theme.spacing(1)}; - } - `, - calloutIcon: css` - color: ${theme.colors.info.text}; - `, - editButton: css` - margin-left: ${theme.spacing(0.5)}; - `, + '& > *': { + marginLeft: theme.spacing(1), + }, + }), + calloutIcon: css({ + color: theme.colors.info.text, + }), + editButton: css({ + marginLeft: theme.spacing(0.5), + }), }); function useColumns(alertManagerSourceName: string) { - const dispatch = useDispatch(); const styles = useStyles2(getStyles); const [updateSupported, updateAllowed] = useAlertmanagerAbility(AlertmanagerAction.UpdateSilence); + const [expireSilence] = alertSilencesApi.endpoints.expireSilence.useMutation(); return useMemo((): SilenceTableColumnProps[] => { - const handleExpireSilenceClick = (id: string) => { - dispatch(expireSilenceAction(alertManagerSourceName, id)); + const handleExpireSilenceClick = (silenceId: string) => { + expireSilence({ datasourceUid: getDatasourceAPIUid(alertManagerSourceName), silenceId }); }; const columns: SilenceTableColumnProps[] = [ { @@ -281,6 +325,6 @@ function useColumns(alertManagerSourceName: string) { }); } return columns; - }, [alertManagerSourceName, dispatch, styles.editButton, updateAllowed, updateSupported]); + }, [alertManagerSourceName, expireSilence, styles.editButton, updateAllowed, updateSupported]); } export default SilencesTable; From 341449f4f52745f6084dbacc42edbbeacba1c78e Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Thu, 25 Apr 2024 16:36:45 +0100 Subject: [PATCH 165/222] Remove unused actions/reducers --- .../alerting/unified/api/alertmanager.ts | 36 ----------- .../alerting/unified/state/actions.ts | 59 ------------------- .../alerting/unified/state/reducers.ts | 8 --- 3 files changed, 103 deletions(-) diff --git a/public/app/features/alerting/unified/api/alertmanager.ts b/public/app/features/alerting/unified/api/alertmanager.ts index 27d080a6c4a..3d5bee320a4 100644 --- a/public/app/features/alerting/unified/api/alertmanager.ts +++ b/public/app/features/alerting/unified/api/alertmanager.ts @@ -11,8 +11,6 @@ import { ExternalAlertmanagersResponse, Matcher, Receiver, - Silence, - SilenceCreatePayload, TestReceiversAlert, TestReceiversPayload, TestReceiversResult, @@ -79,40 +77,6 @@ export async function deleteAlertManagerConfig(alertManagerSourceName: string): ); } -export async function fetchSilences(alertManagerSourceName: string): Promise { - const result = await lastValueFrom( - getBackendSrv().fetch({ - url: `/api/alertmanager/${getDatasourceAPIUid(alertManagerSourceName)}/api/v2/silences`, - showErrorAlert: false, - showSuccessAlert: false, - }) - ); - return result.data; -} - -// returns the new silence ID. Even in the case of an update, a new silence is created and the previous one expired. -export async function createOrUpdateSilence( - alertmanagerSourceName: string, - payload: SilenceCreatePayload -): Promise { - const result = await lastValueFrom( - getBackendSrv().fetch({ - url: `/api/alertmanager/${getDatasourceAPIUid(alertmanagerSourceName)}/api/v2/silences`, - data: payload, - showErrorAlert: false, - showSuccessAlert: false, - method: 'POST', - }) - ); - return result.data; -} - -export async function expireSilence(alertmanagerSourceName: string, silenceID: string): Promise { - await getBackendSrv().delete( - `/api/alertmanager/${getDatasourceAPIUid(alertmanagerSourceName)}/api/v2/silence/${encodeURIComponent(silenceID)}` - ); -} - export async function fetchAlerts( alertmanagerSourceName: string, matchers?: Matcher[], diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index caf8d9db0a0..d362b7282b9 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -4,15 +4,12 @@ import { isEmpty } from 'lodash'; import { locationService } from '@grafana/runtime'; import { logMeasurement } from '@grafana/runtime/src/utils/logging'; import { - AlertmanagerAlert, AlertManagerCortexConfig, AlertmanagerGroup, ExternalAlertmanagerConfig, ExternalAlertmanagersResponse, Matcher, Receiver, - Silence, - SilenceCreatePayload, TestReceiversAlert, } from 'app/plugins/datasource/alertmanager/types'; import { FolderDTO, NotifierDTO, StoreState, ThunkResult } from 'app/types'; @@ -45,14 +42,10 @@ import { } from '../Analytics'; import { addAlertManagers, - createOrUpdateSilence, deleteAlertManagerConfig, - expireSilence, fetchAlertGroups, - fetchAlerts, fetchExternalAlertmanagerConfig, fetchExternalAlertmanagers, - fetchSilences, testReceivers, updateAlertManagerConfig, } from '../api/alertmanager'; @@ -204,17 +197,6 @@ export function fetchPromAndRulerRulesAction({ }; } -export const fetchSilencesAction = createAsyncThunk( - 'unifiedalerting/fetchSilences', - (alertManagerSourceName: string): Promise => { - const fetchSilencesWithLogging = withPerformanceLogging('unifiedalerting/fetchSilences', fetchSilences, { - dataSourceName: alertManagerSourceName, - }); - - return withSerializedError(fetchSilencesWithLogging(alertManagerSourceName)); - } -); - // this will only trigger ruler rules fetch if rules are not loaded yet and request is not in flight export function fetchRulerRulesIfNotFetchedYet(rulesSourceName: string): ThunkResult { return (dispatch, getStore) => { @@ -561,47 +543,6 @@ export const updateAlertManagerConfigAction = createAsyncThunk => - withSerializedError(fetchAlerts(alertManagerSourceName, [], true, true, true)) -); - -export const expireSilenceAction = (alertManagerSourceName: string, silenceId: string): ThunkResult => { - return async (dispatch) => { - await withAppEvents(expireSilence(alertManagerSourceName, silenceId), { - successMessage: 'Silence expired.', - }); - dispatch(fetchSilencesAction(alertManagerSourceName)); - dispatch(fetchAmAlertsAction(alertManagerSourceName)); - }; -}; - -type UpdateSilenceActionOptions = { - alertManagerSourceName: string; - payload: SilenceCreatePayload; - exitOnSave: boolean; - successMessage?: string; -}; - -export const createOrUpdateSilenceAction = createAsyncThunk( - 'unifiedalerting/updateSilence', - ({ alertManagerSourceName, payload, exitOnSave, successMessage }): Promise => - withAppEvents( - withSerializedError( - (async () => { - await createOrUpdateSilence(alertManagerSourceName, payload); - if (exitOnSave) { - locationService.push(makeAMLink('/alerting/silences', alertManagerSourceName)); - } - })() - ), - { - successMessage, - } - ) -); - export const deleteReceiverAction = (receiverName: string, alertManagerSourceName: string): ThunkResult => { return async (dispatch) => { const config = await dispatch( diff --git a/public/app/features/alerting/unified/state/reducers.ts b/public/app/features/alerting/unified/state/reducers.ts index c6e2c1c531b..8783f479877 100644 --- a/public/app/features/alerting/unified/state/reducers.ts +++ b/public/app/features/alerting/unified/state/reducers.ts @@ -3,10 +3,8 @@ import { combineReducers } from 'redux'; import { createAsyncMapSlice, createAsyncSlice } from '../utils/redux'; import { - createOrUpdateSilenceAction, deleteAlertManagerConfigAction, fetchAlertGroupsAction, - fetchAmAlertsAction, fetchEditableRuleAction, fetchExternalAlertmanagersAction, fetchExternalAlertmanagersConfigAction, @@ -16,7 +14,6 @@ import { fetchPromRulesAction, fetchRulerRulesAction, fetchRulesSourceBuildInfoAction, - fetchSilencesAction, saveRuleFormAction, testReceiversAction, updateAlertManagerConfigAction, @@ -32,8 +29,6 @@ export const reducer = combineReducers({ promRules: createAsyncMapSlice('promRules', fetchPromRulesAction, ({ rulesSourceName }) => rulesSourceName).reducer, rulerRules: createAsyncMapSlice('rulerRules', fetchRulerRulesAction, ({ rulesSourceName }) => rulesSourceName) .reducer, - silences: createAsyncMapSlice('silences', fetchSilencesAction, (alertManagerSourceName) => alertManagerSourceName) - .reducer, ruleForm: combineReducers({ saveRule: createAsyncSlice('saveRule', saveRuleFormAction).reducer, existingRule: createAsyncSlice('existingRule', fetchEditableRuleAction).reducer, @@ -41,9 +36,6 @@ export const reducer = combineReducers({ grafanaNotifiers: createAsyncSlice('grafanaNotifiers', fetchGrafanaNotifiersAction).reducer, saveAMConfig: createAsyncSlice('saveAMConfig', updateAlertManagerConfigAction).reducer, deleteAMConfig: createAsyncSlice('deleteAMConfig', deleteAlertManagerConfigAction).reducer, - updateSilence: createAsyncSlice('updateSilence', createOrUpdateSilenceAction).reducer, - amAlerts: createAsyncMapSlice('amAlerts', fetchAmAlertsAction, (alertManagerSourceName) => alertManagerSourceName) - .reducer, folders: createAsyncMapSlice('folders', fetchFolderAction, (uid) => uid).reducer, amAlertGroups: createAsyncMapSlice( 'amAlertGroups', From 10fcd541e36b918b6df3b4be07e86ebf90b99bfc Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Thu, 25 Apr 2024 16:36:57 +0100 Subject: [PATCH 166/222] Add handlers to mock server --- public/app/features/alerting/unified/mockApi.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/features/alerting/unified/mockApi.ts b/public/app/features/alerting/unified/mockApi.ts index 3cce73e348e..77bf8b968fe 100644 --- a/public/app/features/alerting/unified/mockApi.ts +++ b/public/app/features/alerting/unified/mockApi.ts @@ -5,6 +5,7 @@ import { setupServer, SetupServer } from 'msw/node'; import { DataSourceInstanceSettings, PluginMeta } from '@grafana/data'; import { setBackendSrv } from '@grafana/runtime'; import { AlertRuleUpdated } from 'app/features/alerting/unified/api/alertRuleApi'; +import allHandlers from 'app/features/alerting/unified/mocks/server/handlers'; import { DashboardDTO, FolderDTO, NotifierDTO, OrgUser } from 'app/types'; import { PromBuildInfoResponse, @@ -424,7 +425,7 @@ export function mockDashboardApi(server: SetupServer) { }; } -const server = setupServer(); +const server = setupServer(...allHandlers); // Creates a MSW server and sets up beforeAll, afterAll and beforeEach handlers for it export function setupMswServer() { From f3978300984ac0e887a9bbb08f03ebdab8510cf3 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Thu, 25 Apr 2024 16:37:05 +0100 Subject: [PATCH 167/222] Refactor Silences test --- .../alerting/unified/Silences.test.tsx | 218 ++++++------------ 1 file changed, 74 insertions(+), 144 deletions(-) diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index 905ed88d476..92c50bf446e 100644 --- a/public/app/features/alerting/unified/Silences.test.tsx +++ b/public/app/features/alerting/unified/Silences.test.tsx @@ -1,49 +1,35 @@ -import { render, waitFor } from '@testing-library/react'; -import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; import React from 'react'; -import { TestProvider } from 'test/helpers/TestProvider'; +import { render, waitFor, userEvent } from 'test/test-utils'; import { byLabelText, byPlaceholderText, byRole, byTestId, byText } from 'testing-library-selector'; import { dateTime } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { config, locationService, setDataSourceSrv } from '@grafana/runtime'; -import { contextSrv } from 'app/core/services/context_srv'; -import { AlertState, MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; -import { SilenceState } from '../../../plugins/datasource/alertmanager/types'; - import Silences from './Silences'; -import { createOrUpdateSilence, fetchAlerts, fetchSilences } from './api/alertmanager'; -import { grantUserPermissions, mockAlertmanagerAlert, mockDataSource, MockDataSourceSrv, mockSilence } from './mocks'; +import { grantUserPermissions, mockDataSource, MockDataSourceSrv } from './mocks'; import { AlertmanagerProvider } from './state/AlertmanagerContext'; import { setupDataSources } from './testSetup/datasources'; -import { parseMatchers } from './utils/alertmanager'; import { DataSourceType } from './utils/datasource'; -jest.mock('./api/alertmanager'); jest.mock('app/core/services/context_srv'); const TEST_TIMEOUT = 60000; -const mocks = { - api: { - fetchSilences: jest.mocked(fetchSilences), - fetchAlerts: jest.mocked(fetchAlerts), - createOrUpdateSilence: jest.mocked(createOrUpdateSilence), - }, - contextSrv: jest.mocked(contextSrv), -}; - const renderSilences = (location = '/alerting/silences/') => { locationService.push(location); - return render( - - - - - + + + , + { + routerOptions: { + initialEntries: [location], + }, + } ); }; @@ -57,7 +43,8 @@ const dataSources = { const ui = { notExpiredTable: byTestId('not-expired-table'), expiredTable: byTestId('expired-table'), - expiredCaret: byText(/expired/i), + expiredCaret: byText(/expired silences \(/i), + silencesTags: byLabelText(/tags/i), silenceRow: byTestId('row'), silencedAlertCell: byTestId('alerts'), addSilenceButton: byRole('link', { name: /add silence/i }), @@ -80,28 +67,6 @@ const ui = { const resetMocks = () => { jest.resetAllMocks(); - mocks.api.fetchSilences.mockImplementation(() => { - return Promise.resolve([ - mockSilence({ id: '12345' }), - mockSilence({ id: '67890', matchers: parseMatchers('foo!=bar'), comment: 'Catch all' }), - mockSilence({ id: '1111', status: { state: SilenceState.Expired } }), - ]); - }); - - mocks.api.fetchAlerts.mockImplementation(() => { - return Promise.resolve([ - mockAlertmanagerAlert({ - labels: { foo: 'bar' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, - }), - mockAlertmanagerAlert({ - labels: { foo: 'buzz' }, - status: { state: AlertState.Suppressed, silencedBy: ['67890'], inhibitedBy: [] }, - }), - ]); - }); - - mocks.api.createOrUpdateSilence.mockResolvedValue(mockSilence()); grantUserPermissions([ AccessControlAction.AlertingInstanceRead, @@ -117,6 +82,21 @@ const setUserLogged = (isLogged: boolean) => { config.bootData.user.name = isLogged ? 'admin' : ''; }; +const enterSilenceLabel = async (index: number, name: string, matcher: MatcherOperator, value: string) => { + const user = userEvent.setup(); + await user.type(ui.editor.matcherName.getAll()[index], name); + await user.type(ui.editor.matcherOperatorSelect.getAll()[index], matcher); + await user.tab(); + await user.type(ui.editor.matcherValue.getAll()[index], value); +}; + +const addAdditionalMatcher = async () => { + const user = userEvent.setup(); + await user.click(ui.editor.addMatcherButton.get()); +}; + +setupMswServer(); + describe('Silences', () => { beforeAll(resetMocks); afterEach(resetMocks); @@ -128,26 +108,29 @@ describe('Silences', () => { it( 'loads and shows silences', async () => { + const user = userEvent.setup(); renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); - await userEvent.click(ui.expiredCaret.get()); - expect(ui.notExpiredTable.get()).not.toBeNull(); - expect(ui.expiredTable.get()).not.toBeNull(); - let silences = ui.silenceRow.queryAll(); - expect(silences).toHaveLength(3); - expect(silences[0]).toHaveTextContent('foo=bar'); - expect(silences[1]).toHaveTextContent('foo!=bar'); - expect(silences[2]).toHaveTextContent('foo=bar'); + expect(await ui.notExpiredTable.find()).toBeInTheDocument(); - await userEvent.click(ui.expiredCaret.getAll()[0]); - expect(ui.notExpiredTable.get()).not.toBeNull(); - expect(ui.expiredTable.query()).toBeNull(); - silences = ui.silenceRow.queryAll(); - expect(silences).toHaveLength(2); - expect(silences[0]).toHaveTextContent('foo=bar'); - expect(silences[1]).toHaveTextContent('foo!=bar'); + await user.click(ui.expiredCaret.get()); + expect(ui.expiredTable.get()).toBeInTheDocument(); + + const allSilences = ui.silenceRow.queryAll(); + expect(allSilences).toHaveLength(3); + expect(allSilences[0]).toHaveTextContent('foo=bar'); + expect(allSilences[1]).toHaveTextContent('foo!=bar'); + expect(allSilences[2]).toHaveTextContent('foo=bar'); + + await user.click(ui.expiredCaret.get()); + + expect(ui.notExpiredTable.get()).toBeInTheDocument(); + expect(ui.expiredTable.query()).not.toBeInTheDocument(); + + const activeSilences = ui.silenceRow.queryAll(); + expect(activeSilences).toHaveLength(2); + expect(activeSilences[0]).toHaveTextContent('foo=bar'); + expect(activeSilences[1]).toHaveTextContent('foo!=bar'); }, TEST_TIMEOUT ); @@ -155,25 +138,13 @@ describe('Silences', () => { it( 'shows the correct number of silenced alerts', async () => { - mocks.api.fetchAlerts.mockImplementation(() => { - return Promise.resolve([ - mockAlertmanagerAlert({ - labels: { foo: 'bar', buzz: 'bazz' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, - }), - mockAlertmanagerAlert({ - labels: { foo: 'bar', buzz: 'bazz' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, - }), - ]); - }); - renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); - const silencedAlertRows = ui.silencedAlertCell.getAll(ui.notExpiredTable.get()); - expect(silencedAlertRows).toHaveLength(2); + const notExpiredTable = await ui.notExpiredTable.find(); + + expect(notExpiredTable).toBeInTheDocument(); + + const silencedAlertRows = await ui.silencedAlertCell.findAll(notExpiredTable); expect(silencedAlertRows[0]).toHaveTextContent('2'); expect(silencedAlertRows[1]).toHaveTextContent('0'); }, @@ -184,12 +155,9 @@ describe('Silences', () => { 'filters silences by matchers', async () => { renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); - const queryBar = ui.queryBar.get(); - await userEvent.click(queryBar); - await userEvent.paste('foo=bar'); + const queryBar = await ui.queryBar.find(); + await userEvent.type(queryBar, 'foo=bar'); await waitFor(() => expect(ui.silenceRow.getAll()).toHaveLength(2)); }, @@ -199,24 +167,23 @@ describe('Silences', () => { it('shows creating a silence button for users with access', async () => { renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); - - expect(ui.addSilenceButton.get()).toBeInTheDocument(); + expect(await ui.addSilenceButton.find()).toBeInTheDocument(); }); it('hides actions for creating a silence for users without access', async () => { grantUserPermissions([AccessControlAction.AlertingInstanceRead, AccessControlAction.AlertingInstancesExternalRead]); renderSilences(); - await waitFor(() => expect(mocks.api.fetchSilences).toHaveBeenCalled()); - await waitFor(() => expect(mocks.api.fetchAlerts).toHaveBeenCalled()); + + const notExpiredTable = await ui.notExpiredTable.find(); + + expect(notExpiredTable).toBeInTheDocument(); expect(ui.addSilenceButton.query()).not.toBeInTheDocument(); }); }); -describe('Silence edit', () => { +describe('Silence create/edit', () => { const baseUrlPath = '/alerting/silence/new'; beforeAll(resetMocks); afterEach(resetMocks); @@ -242,7 +209,7 @@ describe('Silence edit', () => { const matchersQueryString = matchersParams.map((matcher) => `matcher=${encodeURIComponent(matcher)}`).join('&'); renderSilences(`${baseUrlPath}?${matchersQueryString}`); - await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull()); + expect(await ui.editor.durationField.find()).toBeInTheDocument(); const matchers = ui.editor.matchersField.queryAll(); expect(matchers).toHaveLength(4); @@ -270,7 +237,7 @@ describe('Silence edit', () => { 'creates a new silence', async () => { renderSilences(baseUrlPath); - await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull()); + expect(await ui.editor.durationField.find()).toBeInTheDocument(); const start = new Date(); const end = new Date(start.getTime() + 24 * 60 * 60 * 1000); @@ -285,48 +252,20 @@ describe('Silence edit', () => { await waitFor(() => expect(ui.editor.timeRange.get()).toHaveTextContent(startDateString)); await waitFor(() => expect(ui.editor.timeRange.get()).toHaveTextContent(endDateString)); - await userEvent.type(ui.editor.matcherName.get(), 'foo'); - await userEvent.type(ui.editor.matcherOperatorSelect.get(), '='); - await userEvent.tab(); - await userEvent.type(ui.editor.matcherValue.get(), 'bar'); + await enterSilenceLabel(0, 'foo', MatcherOperator.equal, 'bar'); - // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - await userEvent.click(ui.editor.addMatcherButton.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); - await userEvent.type(ui.editor.matcherName.getAll()[1], 'bar'); - await userEvent.type(ui.editor.matcherOperatorSelect.getAll()[1], '!='); - await userEvent.tab(); - await userEvent.type(ui.editor.matcherValue.getAll()[1], 'buzz'); + await addAdditionalMatcher(); + await enterSilenceLabel(1, 'bar', MatcherOperator.notEqual, 'buzz'); - // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - await userEvent.click(ui.editor.addMatcherButton.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); - await userEvent.type(ui.editor.matcherName.getAll()[2], 'region'); - await userEvent.type(ui.editor.matcherOperatorSelect.getAll()[2], '=~'); - await userEvent.tab(); - await userEvent.type(ui.editor.matcherValue.getAll()[2], 'us-west-.*'); + await addAdditionalMatcher(); + await enterSilenceLabel(2, 'region', MatcherOperator.regex, 'us-west-.*'); - // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed - await userEvent.click(ui.editor.addMatcherButton.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never }); - await userEvent.type(ui.editor.matcherName.getAll()[3], 'env'); - await userEvent.type(ui.editor.matcherOperatorSelect.getAll()[3], '!~'); - await userEvent.tab(); - await userEvent.type(ui.editor.matcherValue.getAll()[3], 'dev|staging'); + await addAdditionalMatcher(); + await enterSilenceLabel(3, 'env', MatcherOperator.notRegex, 'dev|staging'); await userEvent.click(ui.editor.submit.get()); - await waitFor(() => - expect(mocks.api.createOrUpdateSilence).toHaveBeenCalledWith( - 'grafana', - expect.objectContaining({ - comment: expect.stringMatching(/created (\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/), - matchers: [ - { isEqual: true, isRegex: false, name: 'foo', value: 'bar' }, - { isEqual: false, isRegex: false, name: 'bar', value: 'buzz' }, - { isEqual: true, isRegex: true, name: 'region', value: 'us-west-.*' }, - { isEqual: false, isRegex: true, name: 'env', value: 'dev|staging' }, - ], - }) - ) - ); + expect(await ui.notExpiredTable.find()).toBeInTheDocument(); }, TEST_TIMEOUT ); @@ -339,20 +278,11 @@ describe('Silence edit', () => { renderSilences(`${baseUrlPath}?alertmanager=Alertmanager`); await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull()); - await user.type(ui.editor.matcherName.getAll()[0], 'foo'); - await user.type(ui.editor.matcherOperatorSelect.getAll()[0], '='); - await user.type(ui.editor.matcherValue.getAll()[0], 'bar'); + await enterSilenceLabel(0, 'foo', MatcherOperator.equal, 'bar'); await user.click(ui.editor.submit.get()); - await waitFor(() => - expect(mocks.api.createOrUpdateSilence).toHaveBeenCalledWith( - 'Alertmanager', - expect.objectContaining({ - matchers: [{ isEqual: true, isRegex: false, name: 'foo', value: 'bar' }], - }) - ) - ); + expect(await ui.notExpiredTable.find()).toBeInTheDocument(); expect(locationService.getSearch().get('alertmanager')).toBe('Alertmanager'); }, From cdfc6baea414adf01e0234081520e3a3996648ff Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 09:26:54 +0100 Subject: [PATCH 168/222] Remove unused `fetchAlerts` method --- .../alerting/unified/api/alertmanager.ts | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/public/app/features/alerting/unified/api/alertmanager.ts b/public/app/features/alerting/unified/api/alertmanager.ts index 3d5bee320a4..97fd5bfe321 100644 --- a/public/app/features/alerting/unified/api/alertmanager.ts +++ b/public/app/features/alerting/unified/api/alertmanager.ts @@ -1,15 +1,13 @@ import { lastValueFrom } from 'rxjs'; -import { isObject, urlUtil } from '@grafana/data'; +import { isObject } from '@grafana/data'; import { getBackendSrv, isFetchError } from '@grafana/runtime'; import { - AlertmanagerAlert, AlertManagerCortexConfig, AlertmanagerGroup, AlertmanagerStatus, ExternalAlertmanagerConfig, ExternalAlertmanagersResponse, - Matcher, Receiver, TestReceiversAlert, TestReceiversPayload, @@ -77,37 +75,6 @@ export async function deleteAlertManagerConfig(alertManagerSourceName: string): ); } -export async function fetchAlerts( - alertmanagerSourceName: string, - matchers?: Matcher[], - silenced = true, - active = true, - inhibited = true -): Promise { - const filters = - urlUtil.toUrlParams({ silenced, active, inhibited }) + - matchers - ?.map( - (matcher) => - `filter=${encodeURIComponent( - `${escapeQuotes(matcher.name)}=${matcher.isRegex ? '~' : ''}"${escapeQuotes(matcher.value)}"` - )}` - ) - .join('&') || ''; - - const result = await lastValueFrom( - getBackendSrv().fetch({ - url: - `/api/alertmanager/${getDatasourceAPIUid(alertmanagerSourceName)}/api/v2/alerts` + - (filters ? '?' + filters : ''), - showErrorAlert: false, - showSuccessAlert: false, - }) - ); - - return result.data; -} - export async function fetchAlertGroups(alertmanagerSourceName: string): Promise { const result = await lastValueFrom( getBackendSrv().fetch({ @@ -234,7 +201,3 @@ export async function fetchExternalAlertmanagerConfig(): Promise Date: Fri, 26 Apr 2024 09:27:39 +0100 Subject: [PATCH 169/222] Add correct tag invalidation after creating a silence --- public/app/features/alerting/unified/api/alertSilencesApi.ts | 2 +- public/app/features/alerting/unified/api/alertingApi.ts | 3 ++- public/app/features/alerting/unified/api/alertmanagerApi.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/app/features/alerting/unified/api/alertSilencesApi.ts b/public/app/features/alerting/unified/api/alertSilencesApi.ts index 7ecf53ceac1..9f8ba67363c 100644 --- a/public/app/features/alerting/unified/api/alertSilencesApi.ts +++ b/public/app/features/alerting/unified/api/alertSilencesApi.ts @@ -43,7 +43,7 @@ export const alertSilencesApi = alertingApi.injectEndpoints({ method: 'POST', data: payload, }), - invalidatesTags: ['AlertSilences'], + invalidatesTags: ['AlertSilences', 'AlertmanagerAlerts'], }), expireSilence: build.mutation< diff --git a/public/app/features/alerting/unified/api/alertingApi.ts b/public/app/features/alerting/unified/api/alertingApi.ts index 1ee809e24e3..30b2a169da8 100644 --- a/public/app/features/alerting/unified/api/alertingApi.ts +++ b/public/app/features/alerting/unified/api/alertingApi.ts @@ -35,12 +35,13 @@ export const alertingApi = createApi({ tagTypes: [ 'AlertmanagerChoice', 'AlertmanagerConfiguration', + 'AlertmanagerAlerts', + 'AlertSilences', 'OnCallIntegrations', 'OrgMigrationState', 'DataSourceSettings', 'GrafanaLabels', 'CombinedAlertRule', - 'AlertSilences', ], endpoints: () => ({}), }); diff --git a/public/app/features/alerting/unified/api/alertmanagerApi.ts b/public/app/features/alerting/unified/api/alertmanagerApi.ts index ea0f1e948d9..dc605c0bd6f 100644 --- a/public/app/features/alerting/unified/api/alertmanagerApi.ts +++ b/public/app/features/alerting/unified/api/alertmanagerApi.ts @@ -78,6 +78,7 @@ export const alertmanagerApi = alertingApi.injectEndpoints({ params, }; }, + providesTags: ['AlertmanagerAlerts'], }), getAlertmanagerAlertGroups: build.query({ From df5c62b8ad6aa041c0fadc98d124eb4b09269c0b Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 09:27:56 +0100 Subject: [PATCH 170/222] Misc tidy up --- .../components/silences/SilencesEditor.tsx | 2 +- .../components/silences/SilencesTable.tsx | 46 +++++++++---------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx index 1381d54e553..0eedd024c73 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx @@ -195,7 +195,7 @@ export const SilencesEditor = ({ silenceId, alertManagerSourceName }: Props) => return ( -
+
{ - const [getAmAlerts, { data: alertManagerAlerts, isLoading: amAlertsIsLoading }] = - alertmanagerApi.endpoints.getAlertmanagerAlerts.useLazyQuery({ pollingInterval: SILENCES_POLL_INTERVAL_MS }); - const [getSilences, { data: silences = [], isLoading, error }] = alertSilencesApi.endpoints.getSilences.useLazyQuery({ - pollingInterval: SILENCES_POLL_INTERVAL_MS, - }); + const { data: alertManagerAlerts, isLoading: amAlertsIsLoading } = + alertmanagerApi.endpoints.getAlertmanagerAlerts.useQuery( + { amSourceName: alertManagerSourceName, filter: { silenced: true, active: true, inhibited: true } }, + { pollingInterval: SILENCES_POLL_INTERVAL_MS } + ); + + const { + data: silences = [], + isLoading, + error, + } = alertSilencesApi.endpoints.getSilences.useQuery( + { datasourceUid: getDatasourceAPIUid(alertManagerSourceName) }, + { + pollingInterval: SILENCES_POLL_INTERVAL_MS, + } + ); const { currentData: amFeatures } = featureDiscoveryApi.useDiscoverAmFeaturesQuery( { amSourceName: alertManagerSourceName ?? '' }, @@ -51,11 +62,6 @@ const SilencesTable = ({ alertManagerSourceName }: Props) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any (error as any)?.message?.includes('the Alertmanager is not configured') && amFeatures?.lazyConfigInit; - useEffect(() => { - getSilences({ datasourceUid: getDatasourceAPIUid(alertManagerSourceName) }); - getAmAlerts({ amSourceName: alertManagerSourceName }); - }, [alertManagerSourceName, getAmAlerts, getSilences]); - const styles = useStyles2(getStyles); const [queryParams] = useQueryParams(); const filteredSilencesNotExpired = useFilteredSilences(silences, false); @@ -119,11 +125,11 @@ const SilencesTable = ({ alertManagerSourceName }: Props) => { -
+ Add Silence -
+
{ }; const getStyles = (theme: GrafanaTheme2) => ({ - topButtonContainer: css({ - display: 'flex', - flexDirection: 'row', - justifyContent: 'flex-end', - }), addNewSilence: css({ margin: theme.spacing(2, 0), }), @@ -237,13 +238,9 @@ const getStyles = (theme: GrafanaTheme2) => ({ calloutIcon: css({ color: theme.colors.info.text, }), - editButton: css({ - marginLeft: theme.spacing(0.5), - }), }); function useColumns(alertManagerSourceName: string) { - const styles = useStyles2(getStyles); const [updateSupported, updateAllowed] = useAlertmanagerAbility(AlertmanagerAction.UpdateSilence); const [expireSilence] = alertSilencesApi.endpoints.expireSilence.useMutation(); @@ -312,10 +309,9 @@ function useColumns(alertManagerSourceName: string) { )} {silence.status.state !== 'expired' && ( )} @@ -325,6 +321,6 @@ function useColumns(alertManagerSourceName: string) { }); } return columns; - }, [alertManagerSourceName, expireSilence, styles.editButton, updateAllowed, updateSupported]); + }, [alertManagerSourceName, expireSilence, updateAllowed, updateSupported]); } export default SilencesTable; From f419c9b53a67ab9e1c0870f80837e1f24e204d16 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 09:45:00 +0100 Subject: [PATCH 171/222] Add comment explaining reset in silences editor --- .../alerting/unified/components/silences/SilencesEditor.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx index 0eedd024c73..4a87736a46f 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx @@ -139,6 +139,7 @@ export const SilencesEditor = ({ silenceId, alertManagerSourceName }: Props) => const matcherFields = watch('matchers'); useEffect(() => { + // Allows the form to correctly initialise when an existing silence is fetch from the backend reset(getDefaultFormValues(urlSearchParams, silence)); }, [reset, silence, urlSearchParams]); From 0dc003aadcacc0e85a45cba2cbe4e0b134c2e400 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 11:19:14 +0100 Subject: [PATCH 172/222] Split handlers out into separate files --- .../alerting/unified/mocks/alertmanagerApi.ts | 23 +++++++- .../alerting/unified/mocks/datasources.ts | 5 ++ .../unified/mocks/server/configure.ts | 2 +- .../alerting/unified/mocks/server/handlers.ts | 56 +++---------------- .../alerting/unified/mocks/silences.ts | 15 +++++ 5 files changed, 51 insertions(+), 50 deletions(-) create mode 100644 public/app/features/alerting/unified/mocks/datasources.ts create mode 100644 public/app/features/alerting/unified/mocks/silences.ts diff --git a/public/app/features/alerting/unified/mocks/alertmanagerApi.ts b/public/app/features/alerting/unified/mocks/alertmanagerApi.ts index 1db4862f414..b5ad61cc0b1 100644 --- a/public/app/features/alerting/unified/mocks/alertmanagerApi.ts +++ b/public/app/features/alerting/unified/mocks/alertmanagerApi.ts @@ -1,9 +1,12 @@ import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/node'; +import { mockAlertmanagerAlert } from 'app/features/alerting/unified/mocks'; + import { AlertmanagerChoice, AlertManagerCortexConfig, + AlertState, ExternalAlertmanagersResponse, } from '../../../../plugins/datasource/alertmanager/types'; import { AlertmanagersChoiceResponse } from '../api/alertmanagerApi'; @@ -13,8 +16,12 @@ export const defaultAlertmanagerChoiceResponse: AlertmanagersChoiceResponse = { alertmanagersChoice: AlertmanagerChoice.Internal, numExternalAlertmanagers: 0, }; + +export const alertmanagerChoiceHandler = (response = defaultAlertmanagerChoiceResponse) => + http.get('/api/v1/ngalert', () => HttpResponse.json(response)); + export function mockAlertmanagerChoiceResponse(server: SetupServer, response: AlertmanagersChoiceResponse) { - server.use(http.get('/api/v1/ngalert', () => HttpResponse.json(response))); + server.use(alertmanagerChoiceHandler(response)); } export const emptyExternalAlertmanagersResponse: ExternalAlertmanagersResponse = { @@ -38,3 +45,17 @@ export function mockAlertmanagerConfigResponse( ) ); } + +export const alertmanagerAlertsListHandler = () => + http.get('/api/alertmanager/:datasourceUid/api/v2/alerts', () => + HttpResponse.json([ + mockAlertmanagerAlert({ + labels: { foo: 'bar', buzz: 'bazz' }, + status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, + }), + mockAlertmanagerAlert({ + labels: { foo: 'bar', buzz: 'bazz' }, + status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, + }), + ]) + ); diff --git a/public/app/features/alerting/unified/mocks/datasources.ts b/public/app/features/alerting/unified/mocks/datasources.ts new file mode 100644 index 00000000000..0ff5d12bf65 --- /dev/null +++ b/public/app/features/alerting/unified/mocks/datasources.ts @@ -0,0 +1,5 @@ +import { HttpResponse, http } from 'msw'; + +// TODO: Add more accurate endpoint responses as tests require +export const datasourceBuildInfoHandler = () => + http.get('/api/datasources/proxy/uid/:datasourceUid/api/v1/status/buildinfo', () => HttpResponse.json({})); diff --git a/public/app/features/alerting/unified/mocks/server/configure.ts b/public/app/features/alerting/unified/mocks/server/configure.ts index 27e36830616..04b17f571ac 100644 --- a/public/app/features/alerting/unified/mocks/server/configure.ts +++ b/public/app/features/alerting/unified/mocks/server/configure.ts @@ -1,5 +1,5 @@ import server from 'app/features/alerting/unified/mockApi'; -import { alertmanagerChoiceHandler } from 'app/features/alerting/unified/mocks/server/handlers'; +import { alertmanagerChoiceHandler } from 'app/features/alerting/unified/mocks/alertmanagerApi'; import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; /** diff --git a/public/app/features/alerting/unified/mocks/server/handlers.ts b/public/app/features/alerting/unified/mocks/server/handlers.ts index aea95b81cf5..78bc9baa86c 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers.ts @@ -1,53 +1,13 @@ /** - * Contains definitions for all handlers that are required for test rendering of components within Alerting + * Contains all handlers that are required for test rendering of components within Alerting */ -import { HttpResponse, http } from 'msw'; - -import { mockAlertmanagerAlert, mockSilences } from 'app/features/alerting/unified/mocks'; -import { defaultAlertmanagerChoiceResponse } from 'app/features/alerting/unified/mocks/alertmanagerApi'; -import { AlertState } from 'app/plugins/datasource/alertmanager/types'; - -/////////////////// -// Alertmanagers // -/////////////////// - -export const alertmanagerChoiceHandler = (response = defaultAlertmanagerChoiceResponse) => - http.get('/api/v1/ngalert', () => HttpResponse.json(response)); - -const alertmanagerAlertsListHandler = () => - http.get('/api/alertmanager/:datasourceUid/api/v2/alerts', () => - HttpResponse.json([ - mockAlertmanagerAlert({ - labels: { foo: 'bar', buzz: 'bazz' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, - }), - mockAlertmanagerAlert({ - labels: { foo: 'bar', buzz: 'bazz' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, - }), - ]) - ); - -///////////////// -// Datasources // -///////////////// - -// TODO: Add more accurate endpoint responses as tests require -const datasourceBuildInfoHandler = () => - http.get('/api/datasources/proxy/uid/:datasourceUid/api/v1/status/buildinfo', () => HttpResponse.json({})); - -////////////// -// Silences // -////////////// - -const silencesListHandler = (silences = mockSilences) => - http.get('/api/alertmanager/:datasourceUid/api/v2/silences', () => HttpResponse.json(silences)); - -const createSilenceHandler = () => - http.post('/api/alertmanager/:datasourceUid/api/v2/silences', () => - HttpResponse.json({ silenceId: '4bda5b38-7939-4887-9ec2-16323b8e3b4e' }) - ); +import { + alertmanagerAlertsListHandler, + alertmanagerChoiceHandler, +} from 'app/features/alerting/unified/mocks/alertmanagerApi'; +import { datasourceBuildInfoHandler } from 'app/features/alerting/unified/mocks/datasources'; +import { silenceCreateHandler, silencesListHandler } from 'app/features/alerting/unified/mocks/silences'; /** * All mock handlers that are required across Alerting tests @@ -55,7 +15,7 @@ const createSilenceHandler = () => const allHandlers = [ alertmanagerChoiceHandler(), silencesListHandler(), - createSilenceHandler(), + silenceCreateHandler(), alertmanagerAlertsListHandler(), datasourceBuildInfoHandler(), ]; diff --git a/public/app/features/alerting/unified/mocks/silences.ts b/public/app/features/alerting/unified/mocks/silences.ts new file mode 100644 index 00000000000..e23e6ea9ded --- /dev/null +++ b/public/app/features/alerting/unified/mocks/silences.ts @@ -0,0 +1,15 @@ +import { HttpResponse, http } from 'msw'; + +import { mockSilences } from 'app/features/alerting/unified/mocks'; + +////////////// +// Silences // +////////////// + +export const silencesListHandler = (silences = mockSilences) => + http.get('/api/alertmanager/:datasourceUid/api/v2/silences', () => HttpResponse.json(silences)); + +export const silenceCreateHandler = () => + http.post('/api/alertmanager/:datasourceUid/api/v2/silences', () => + HttpResponse.json({ silenceId: '4bda5b38-7939-4887-9ec2-16323b8e3b4e' }) + ); From 4d4bf391846492911cc0ecc22c0c6fda146846d6 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 11:20:08 +0100 Subject: [PATCH 173/222] Check for server received body This is not ideal! Preference would be to have a more robust mock server that responds to the received silence and appends it to a stateful list for the test and then resets afterwards --- .../alerting/unified/Silences.test.tsx | 28 +++++++++++++++++++ .../alerting/unified/mocks/server/events.ts | 23 +++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 public/app/features/alerting/unified/mocks/server/events.ts diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index 92c50bf446e..0ac440359d5 100644 --- a/public/app/features/alerting/unified/Silences.test.tsx +++ b/public/app/features/alerting/unified/Silences.test.tsx @@ -6,6 +6,8 @@ import { dateTime } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { config, locationService, setDataSourceSrv } from '@grafana/runtime'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { waitForServerRequest } from 'app/features/alerting/unified/mocks/server/events'; +import { silenceCreateHandler } from 'app/features/alerting/unified/mocks/silences'; import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; @@ -239,6 +241,8 @@ describe('Silence create/edit', () => { renderSilences(baseUrlPath); expect(await ui.editor.durationField.find()).toBeInTheDocument(); + const postRequest = waitForServerRequest(silenceCreateHandler()); + const start = new Date(); const end = new Date(start.getTime() + 24 * 60 * 60 * 1000); @@ -266,6 +270,20 @@ describe('Silence create/edit', () => { await userEvent.click(ui.editor.submit.get()); expect(await ui.notExpiredTable.find()).toBeInTheDocument(); + + const createSilenceRequest = await postRequest; + const requestBody = await createSilenceRequest.clone().json(); + expect(requestBody).toMatchObject( + expect.objectContaining({ + comment: expect.stringMatching(/created (\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/), + matchers: [ + { isEqual: true, isRegex: false, name: 'foo', value: 'bar' }, + { isEqual: false, isRegex: false, name: 'bar', value: 'buzz' }, + { isEqual: true, isRegex: true, name: 'region', value: 'us-west-.*' }, + { isEqual: false, isRegex: true, name: 'env', value: 'dev|staging' }, + ], + }) + ); }, TEST_TIMEOUT ); @@ -275,6 +293,8 @@ describe('Silence create/edit', () => { async () => { const user = userEvent.setup(); + const postRequest = waitForServerRequest(silenceCreateHandler()); + renderSilences(`${baseUrlPath}?alertmanager=Alertmanager`); await waitFor(() => expect(ui.editor.durationField.query()).not.toBeNull()); @@ -285,6 +305,14 @@ describe('Silence create/edit', () => { expect(await ui.notExpiredTable.find()).toBeInTheDocument(); expect(locationService.getSearch().get('alertmanager')).toBe('Alertmanager'); + + const createSilenceRequest = await postRequest; + const requestBody = await createSilenceRequest.clone().json(); + expect(requestBody).toMatchObject( + expect.objectContaining({ + matchers: [{ isEqual: true, isRegex: false, name: 'foo', value: 'bar' }], + }) + ); }, TEST_TIMEOUT ); diff --git a/public/app/features/alerting/unified/mocks/server/events.ts b/public/app/features/alerting/unified/mocks/server/events.ts new file mode 100644 index 00000000000..1feb57d5a89 --- /dev/null +++ b/public/app/features/alerting/unified/mocks/server/events.ts @@ -0,0 +1,23 @@ +import { HttpHandler, matchRequestUrl } from 'msw'; + +import server from 'app/features/alerting/unified/mockApi'; + +/** + * Wait for the mock server to receive a request for the given method + url combination, + * and resolve with information about the request that was made + * + * @deprecated Try not to use this 🙏 instead aim to assert against UI side effects + */ +export function waitForServerRequest(handler: HttpHandler) { + const { method, path } = handler.info; + return new Promise((resolve) => { + server.events.on('request:match', ({ request }) => { + const matchesMethod = request.method.toLowerCase() === String(method).toLowerCase(); + const matchesUrl = matchRequestUrl(new URL(request.url), path); + + if (matchesMethod && matchesUrl) { + resolve(request); + } + }); + }); +} From 9bf2cf0a52cefa09c08579e4f60206ef961b1108 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 11:27:18 +0100 Subject: [PATCH 174/222] Rename silences RTKQ tag --- .../app/features/alerting/unified/api/alertSilencesApi.ts | 8 ++++---- public/app/features/alerting/unified/api/alertingApi.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/app/features/alerting/unified/api/alertSilencesApi.ts b/public/app/features/alerting/unified/api/alertSilencesApi.ts index 9f8ba67363c..a89686243c9 100644 --- a/public/app/features/alerting/unified/api/alertSilencesApi.ts +++ b/public/app/features/alerting/unified/api/alertSilencesApi.ts @@ -13,7 +13,7 @@ export const alertSilencesApi = alertingApi.injectEndpoints({ query: ({ datasourceUid }) => ({ url: `/api/alertmanager/${datasourceUid}/api/v2/silences`, }), - providesTags: ['AlertSilences'], + providesTags: ['AlertmanagerSilences'], }), getSilence: build.query< @@ -26,7 +26,7 @@ export const alertSilencesApi = alertingApi.injectEndpoints({ query: ({ datasourceUid, id }) => ({ url: `/api/alertmanager/${datasourceUid}/api/v2/silence/${id}`, }), - providesTags: ['AlertSilences'], + providesTags: ['AlertmanagerSilences'], }), createSilence: build.mutation< @@ -43,7 +43,7 @@ export const alertSilencesApi = alertingApi.injectEndpoints({ method: 'POST', data: payload, }), - invalidatesTags: ['AlertSilences', 'AlertmanagerAlerts'], + invalidatesTags: ['AlertmanagerSilences', 'AlertmanagerAlerts'], }), expireSilence: build.mutation< @@ -59,7 +59,7 @@ export const alertSilencesApi = alertingApi.injectEndpoints({ url: `/api/alertmanager/${datasourceUid}/api/v2/silence/${silenceId}`, method: 'DELETE', }), - invalidatesTags: ['AlertSilences'], + invalidatesTags: ['AlertmanagerSilences'], }), }), }); diff --git a/public/app/features/alerting/unified/api/alertingApi.ts b/public/app/features/alerting/unified/api/alertingApi.ts index 30b2a169da8..bb1dd3071d4 100644 --- a/public/app/features/alerting/unified/api/alertingApi.ts +++ b/public/app/features/alerting/unified/api/alertingApi.ts @@ -36,7 +36,7 @@ export const alertingApi = createApi({ 'AlertmanagerChoice', 'AlertmanagerConfiguration', 'AlertmanagerAlerts', - 'AlertSilences', + 'AlertmanagerSilences', 'OnCallIntegrations', 'OrgMigrationState', 'DataSourceSettings', From a0373c66c3e6ad648bff0858119ffa7ad13b6e1f Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 11:27:31 +0100 Subject: [PATCH 175/222] Add fallback for alertmanager alerts --- .../alerting/unified/components/silences/SilencesTable.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx index 5b420dc7ea5..ba5691f5389 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx @@ -36,7 +36,7 @@ interface Props { } const SilencesTable = ({ alertManagerSourceName }: Props) => { - const { data: alertManagerAlerts, isLoading: amAlertsIsLoading } = + const { data: alertManagerAlerts = [], isLoading: amAlertsIsLoading } = alertmanagerApi.endpoints.getAlertmanagerAlerts.useQuery( { amSourceName: alertManagerSourceName, filter: { silenced: true, active: true, inhibited: true } }, { pollingInterval: SILENCES_POLL_INTERVAL_MS } @@ -72,7 +72,7 @@ const SilencesTable = ({ alertManagerSourceName }: Props) => { const itemsNotExpired = useMemo((): SilenceTableItemProps[] => { const findSilencedAlerts = (id: string) => { - return (alertManagerAlerts || []).filter((alert) => alert.status.silencedBy.includes(id)); + return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id)); }; return filteredSilencesNotExpired.map((silence) => { const silencedAlerts = findSilencedAlerts(silence.id); @@ -85,7 +85,7 @@ const SilencesTable = ({ alertManagerSourceName }: Props) => { const itemsExpired = useMemo((): SilenceTableItemProps[] => { const findSilencedAlerts = (id: string) => { - return (alertManagerAlerts || []).filter((alert) => alert.status.silencedBy.includes(id)); + return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id)); }; return filteredSilencesExpired.map((silence) => { const silencedAlerts = findSilencedAlerts(silence.id); From 4fb1ac2bec1e85db9f16757255280ce269380bd7 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 11:29:47 +0100 Subject: [PATCH 176/222] Check error state more reliably --- .betterer.results | 4 ---- .../unified/components/silences/SilencesTable.tsx | 9 ++++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.betterer.results b/.betterer.results index 66562bf277d..7bd18516f5a 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2158,10 +2158,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "3"], [0, 0, 0, "Styles should be written using objects.", "4"] ], - "public/app/features/alerting/unified/components/silences/SilencesTable.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] - ], "public/app/features/alerting/unified/hooks/useAlertmanagerConfig.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], diff --git a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx index ba5691f5389..d2d63114ed9 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx @@ -2,6 +2,7 @@ import { css } from '@emotion/css'; import React, { useMemo } from 'react'; import { dateMath, GrafanaTheme2 } from '@grafana/data'; +import { isFetchError } from '@grafana/runtime'; import { CollapsableSection, Icon, Link, LinkButton, useStyles2, Stack, Alert } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { alertSilencesApi } from 'app/features/alerting/unified/api/alertSilencesApi'; @@ -59,8 +60,7 @@ const SilencesTable = ({ alertManagerSourceName }: Props) => { ); const mimirLazyInitError = - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (error as any)?.message?.includes('the Alertmanager is not configured') && amFeatures?.lazyConfigInit; + isFetchError(error) && error?.message?.includes('the Alertmanager is not configured') && amFeatures?.lazyConfigInit; const styles = useStyles2(getStyles); const [queryParams] = useQueryParams(); @@ -109,9 +109,8 @@ const SilencesTable = ({ alertManagerSourceName }: Props) => { ); } - if (error) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const errMessage = (error as any)?.message || 'Unknown error.'; + if (isFetchError(error)) { + const errMessage = error?.message || 'Unknown error.'; return ( {errMessage} From 9860117399a068e693912547643a1608547e4cc7 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 15:42:41 +0100 Subject: [PATCH 177/222] Add more fine grained tag invalidation/providing for Silences --- .../app/features/alerting/unified/api/alertSilencesApi.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/public/app/features/alerting/unified/api/alertSilencesApi.ts b/public/app/features/alerting/unified/api/alertSilencesApi.ts index a89686243c9..ad59bf29176 100644 --- a/public/app/features/alerting/unified/api/alertSilencesApi.ts +++ b/public/app/features/alerting/unified/api/alertSilencesApi.ts @@ -13,7 +13,8 @@ export const alertSilencesApi = alertingApi.injectEndpoints({ query: ({ datasourceUid }) => ({ url: `/api/alertmanager/${datasourceUid}/api/v2/silences`, }), - providesTags: ['AlertmanagerSilences'], + providesTags: (result) => + result ? result.map(({ id }) => ({ type: 'AlertmanagerSilences', id })) : ['AlertmanagerSilences'], }), getSilence: build.query< @@ -25,8 +26,10 @@ export const alertSilencesApi = alertingApi.injectEndpoints({ >({ query: ({ datasourceUid, id }) => ({ url: `/api/alertmanager/${datasourceUid}/api/v2/silence/${id}`, + showErrorAlert: false, }), - providesTags: ['AlertmanagerSilences'], + providesTags: (result, error, { id }) => + result ? [{ type: 'AlertmanagerSilences', id }] : ['AlertmanagerSilences'], }), createSilence: build.mutation< From a34c02fcf2ae2bbb58667ff74596f53a71171f17 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 15:45:34 +0100 Subject: [PATCH 178/222] Address misc PR feedback --- .../alerting/unified/Silences.test.tsx | 12 +++--- .../components/silences/SilencesEditor.tsx | 39 ++++++++++++++----- .../components/silences/SilencesTable.tsx | 19 ++++----- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index 0ac440359d5..0d9d0a44f1d 100644 --- a/public/app/features/alerting/unified/Silences.test.tsx +++ b/public/app/features/alerting/unified/Silences.test.tsx @@ -28,7 +28,7 @@ const renderSilences = (location = '/alerting/silences/') => { , { - routerOptions: { + historyOptions: { initialEntries: [location], }, } @@ -156,10 +156,11 @@ describe('Silences', () => { it( 'filters silences by matchers', async () => { + const user = userEvent.setup(); renderSilences(); const queryBar = await ui.queryBar.find(); - await userEvent.type(queryBar, 'foo=bar'); + await user.type(queryBar, 'foo=bar'); await waitFor(() => expect(ui.silenceRow.getAll()).toHaveLength(2)); }, @@ -238,6 +239,7 @@ describe('Silence create/edit', () => { it( 'creates a new silence', async () => { + const user = userEvent.setup(); renderSilences(baseUrlPath); expect(await ui.editor.durationField.find()).toBeInTheDocument(); @@ -249,8 +251,8 @@ describe('Silence create/edit', () => { const startDateString = dateTime(start).format('YYYY-MM-DD'); const endDateString = dateTime(end).format('YYYY-MM-DD'); - await userEvent.clear(ui.editor.durationInput.get()); - await userEvent.type(ui.editor.durationInput.get(), '1d'); + await user.clear(ui.editor.durationInput.get()); + await user.type(ui.editor.durationInput.get(), '1d'); await waitFor(() => expect(ui.editor.durationInput.query()).toHaveValue('1d')); await waitFor(() => expect(ui.editor.timeRange.get()).toHaveTextContent(startDateString)); @@ -267,7 +269,7 @@ describe('Silence create/edit', () => { await addAdditionalMatcher(); await enterSilenceLabel(3, 'env', MatcherOperator.notRegex, 'dev|staging'); - await userEvent.click(ui.editor.submit.get()); + await user.click(ui.editor.submit.get()); expect(await ui.notExpiredTable.find()).toBeInTheDocument(); diff --git a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx index 4a87736a46f..1bad2e84fa6 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx @@ -2,7 +2,6 @@ import { css, cx } from '@emotion/css'; import { isEqual, pickBy } from 'lodash'; import React, { useEffect, useMemo, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; -import { useHistory } from 'react-router'; import { useDebounce } from 'react-use'; import { @@ -14,8 +13,18 @@ import { isValidDate, parseDuration, } from '@grafana/data'; -import { config } from '@grafana/runtime'; -import { Button, Field, FieldSet, Input, LinkButton, TextArea, useStyles2 } from '@grafana/ui'; +import { config, isFetchError, locationService } from '@grafana/runtime'; +import { + Alert, + Button, + Field, + FieldSet, + Input, + LinkButton, + LoadingPlaceholder, + TextArea, + useStyles2, +} from '@grafana/ui'; import { alertSilencesApi } from 'app/features/alerting/unified/api/alertSilencesApi'; import { getDatasourceAPIUid } from 'app/features/alerting/unified/utils/datasource'; import { Matcher, MatcherOperator, Silence, SilenceCreatePayload } from 'app/plugins/datasource/alertmanager/types'; @@ -96,8 +105,9 @@ const getDefaultFormValues = (searchParams: URLSearchParams, silence?: Silence): }; export const SilencesEditor = ({ silenceId, alertManagerSourceName }: Props) => { - const history = useHistory(); - const [getSilence, { data: silence, isLoading: getSilenceIsLoading }] = + // Use a lazy query to fetch the Silence info, as we may not always require this + // (e.g. if creating a new one from scratch, we don't need to fetch anything) + const [getSilence, { data: silence, isLoading: getSilenceIsLoading, error: errorGettingExistingSilence }] = alertSilencesApi.endpoints.getSilence.useLazyQuery(); const [createSilence, { isLoading }] = alertSilencesApi.endpoints.createSilence.useMutation(); const [urlSearchParams] = useURLSearchParams(); @@ -129,7 +139,7 @@ export const SilencesEditor = ({ silenceId, alertManagerSourceName }: Props) => await createSilence({ datasourceUid: getDatasourceAPIUid(alertManagerSourceName), payload }) .unwrap() .then(() => { - history.push(makeAMLink('/alerting/silences', alertManagerSourceName)); + locationService.push(makeAMLink('/alerting/silences', alertManagerSourceName)); }); }; @@ -139,8 +149,10 @@ export const SilencesEditor = ({ silenceId, alertManagerSourceName }: Props) => const matcherFields = watch('matchers'); useEffect(() => { - // Allows the form to correctly initialise when an existing silence is fetch from the backend - reset(getDefaultFormValues(urlSearchParams, silence)); + if (silence) { + // Allows the form to correctly initialise when an existing silence is fetch from the backend + reset(getDefaultFormValues(urlSearchParams, silence)); + } }, [reset, silence, urlSearchParams]); useEffect(() => { @@ -190,13 +202,20 @@ export const SilencesEditor = ({ silenceId, alertManagerSourceName }: Props) => const userLogged = Boolean(config.bootData.user.isSignedIn && config.bootData.user.name); if (getSilenceIsLoading) { - return null; + return ; + } + + const existingSilenceNotFound = + isFetchError(errorGettingExistingSilence) && errorGettingExistingSilence.status === 404; + + if (existingSilenceNotFound) { + return ; } return ( -
+
{ const { data: alertManagerAlerts = [], isLoading: amAlertsIsLoading } = alertmanagerApi.endpoints.getAlertmanagerAlerts.useQuery( { amSourceName: alertManagerSourceName, filter: { silenced: true, active: true, inhibited: true } }, - { pollingInterval: SILENCES_POLL_INTERVAL_MS } + API_QUERY_OPTIONS ); const { @@ -49,9 +51,7 @@ const SilencesTable = ({ alertManagerSourceName }: Props) => { error, } = alertSilencesApi.endpoints.getSilences.useQuery( { datasourceUid: getDatasourceAPIUid(alertManagerSourceName) }, - { - pollingInterval: SILENCES_POLL_INTERVAL_MS, - } + API_QUERY_OPTIONS ); const { currentData: amFeatures } = featureDiscoveryApi.useDiscoverAmFeaturesQuery( @@ -60,7 +60,7 @@ const SilencesTable = ({ alertManagerSourceName }: Props) => { ); const mimirLazyInitError = - isFetchError(error) && error?.message?.includes('the Alertmanager is not configured') && amFeatures?.lazyConfigInit; + stringifyErrorLike(error).includes('the Alertmanager is not configured') && amFeatures?.lazyConfigInit; const styles = useStyles2(getStyles); const [queryParams] = useQueryParams(); @@ -97,7 +97,7 @@ const SilencesTable = ({ alertManagerSourceName }: Props) => { }, [filteredSilencesExpired, alertManagerAlerts]); if (isLoading || amAlertsIsLoading) { - return null; + return ; } if (mimirLazyInitError) { @@ -218,9 +218,6 @@ const useFilteredSilences = (silences: Silence[], expired = false) => { }; const getStyles = (theme: GrafanaTheme2) => ({ - addNewSilence: css({ - margin: theme.spacing(2, 0), - }), callout: css({ backgroundColor: theme.colors.background.secondary, borderTop: `3px solid ${theme.colors.info.border}`, From 31231cf5bf5c966f468b000827687d41bef62f05 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 15:45:48 +0100 Subject: [PATCH 179/222] Change test render method to use locationService --- public/test/test-utils.tsx | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/public/test/test-utils.tsx b/public/test/test-utils.tsx index b27a0666b54..ee6d2b6c2e7 100644 --- a/public/test/test-utils.tsx +++ b/public/test/test-utils.tsx @@ -1,12 +1,14 @@ import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore'; import { render, RenderOptions } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import React, { ComponentProps, Fragment, PropsWithChildren } from 'react'; +import { createMemoryHistory, MemoryHistoryBuildOptions } from 'history'; +import React, { Fragment, PropsWithChildren } from 'react'; import { Provider } from 'react-redux'; -import { MemoryRouter } from 'react-router-dom'; +import { Router } from 'react-router-dom'; import { PreloadedState } from 'redux'; import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; +import { HistoryWrapper, setLocationService } from '@grafana/runtime'; import { GrafanaContext, GrafanaContextType } from 'app/core/context/GrafanaContext'; import { ModalsContextProvider } from 'app/core/context/ModalsContextProvider'; import { configureStore } from 'app/store/configureStore'; @@ -29,9 +31,9 @@ interface ExtendedRenderOptions extends RenderOptions { */ renderWithRouter?: boolean; /** - * Props to pass to `MemoryRouter`, if being used + * Props to pass to `createMemoryHistory`, if being used */ - routerOptions?: ComponentProps; + historyOptions?: MemoryHistoryBuildOptions; } /** @@ -41,7 +43,7 @@ interface ExtendedRenderOptions extends RenderOptions { const getWrapper = ({ store, renderWithRouter, - routerOptions, + historyOptions, grafanaContext, }: ExtendedRenderOptions & { grafanaContext?: GrafanaContextType; @@ -50,7 +52,13 @@ const getWrapper = ({ /** * Conditional router - either a MemoryRouter or just a Fragment */ - const PotentialRouter = renderWithRouter ? MemoryRouter : Fragment; + const PotentialRouter = renderWithRouter ? Router : Fragment; + + // Create a fresh location service for each test - otherwise we run the risk + // of it being stateful in between runs + const history = createMemoryHistory(historyOptions); + const locationService = new HistoryWrapper(history); + setLocationService(locationService); const context = { ...getGrafanaContextMock(), @@ -65,7 +73,7 @@ const getWrapper = ({ return ( - + {children} From fd07b3d4311e0966a3fc3ab35b7df57b6803fbd0 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 16:01:49 +0100 Subject: [PATCH 180/222] Link silence test handlers together more clearly and test more cases --- .../alerting/unified/Silences.test.tsx | 19 ++++++++++++++++++- public/app/features/alerting/unified/mocks.ts | 12 +++++++++--- .../alerting/unified/mocks/alertmanagerApi.ts | 6 +++--- .../alerting/unified/mocks/server/handlers.ts | 13 ++++++++++--- .../alerting/unified/mocks/silences.ts | 11 +++++++++++ 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index 0d9d0a44f1d..84c35a27d93 100644 --- a/public/app/features/alerting/unified/Silences.test.tsx +++ b/public/app/features/alerting/unified/Silences.test.tsx @@ -12,7 +12,7 @@ import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; import Silences from './Silences'; -import { grantUserPermissions, mockDataSource, MockDataSourceSrv } from './mocks'; +import { grantUserPermissions, MOCK_SILENCE_ID_EXISTING, mockDataSource, MockDataSourceSrv } from './mocks'; import { AlertmanagerProvider } from './state/AlertmanagerContext'; import { setupDataSources } from './testSetup/datasources'; import { DataSourceType } from './utils/datasource'; @@ -51,6 +51,7 @@ const ui = { silencedAlertCell: byTestId('alerts'), addSilenceButton: byRole('link', { name: /add silence/i }), queryBar: byPlaceholderText('Search'), + existingSilenceNotFound: byRole('alert', { name: /existing silence .* not found/i }), editor: { timeRange: byTestId(selectors.components.TimePicker.openButton), durationField: byLabelText('Duration'), @@ -290,6 +291,22 @@ describe('Silence create/edit', () => { TEST_TIMEOUT ); + it('shows an error when existing silence cannot be found', async () => { + renderSilences('/alerting/silence/foo-bar/edit'); + + expect(await ui.existingSilenceNotFound.find()).toBeInTheDocument(); + }); + + it('populates form with existing silence information', async () => { + renderSilences(`/alerting/silence/${MOCK_SILENCE_ID_EXISTING}/edit`); + + // Await the first value to be populated, after which we can expect that all of the other + // existing fields have been filled out as well + await waitFor(() => expect(ui.editor.matcherName.get()).toHaveValue('foo')); + expect(ui.editor.matcherValue.get()).toHaveValue('bar'); + expect(ui.editor.comment.get()).toHaveValue('Silence noisy alerts'); + }); + it( 'silences page should contain alertmanager parameter after creating a silence', async () => { diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 978cf70a859..7ac3c3dfb13 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -307,10 +307,16 @@ export const mockSilence = (partial: Partial = {}): Silence => { }; }; +export const MOCK_SILENCE_ID_EXISTING = 'f209e273-0e4e-434f-9f66-e72f092025a2'; + export const mockSilences = [ - mockSilence({ id: '12345' }), - mockSilence({ id: '67890', matchers: parseMatchers('foo!=bar'), comment: 'Catch all' }), - mockSilence({ id: '1111', status: { state: SilenceState.Expired } }), + mockSilence({ id: MOCK_SILENCE_ID_EXISTING }), + mockSilence({ + id: 'ce031625-61c7-47cd-9beb-8760bccf0ed7', + matchers: parseMatchers('foo!=bar'), + comment: 'Catch all', + }), + mockSilence({ id: '145884a8-ee20-4864-9f84-661305fb7d82', status: { state: SilenceState.Expired } }), ]; export const mockNotifiersState = (partial: Partial = {}): NotifiersState => { diff --git a/public/app/features/alerting/unified/mocks/alertmanagerApi.ts b/public/app/features/alerting/unified/mocks/alertmanagerApi.ts index b5ad61cc0b1..9c30d8ef699 100644 --- a/public/app/features/alerting/unified/mocks/alertmanagerApi.ts +++ b/public/app/features/alerting/unified/mocks/alertmanagerApi.ts @@ -1,7 +1,7 @@ import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/node'; -import { mockAlertmanagerAlert } from 'app/features/alerting/unified/mocks'; +import { MOCK_SILENCE_ID_EXISTING, mockAlertmanagerAlert } from 'app/features/alerting/unified/mocks'; import { AlertmanagerChoice, @@ -51,11 +51,11 @@ export const alertmanagerAlertsListHandler = () => HttpResponse.json([ mockAlertmanagerAlert({ labels: { foo: 'bar', buzz: 'bazz' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, + status: { state: AlertState.Suppressed, silencedBy: [MOCK_SILENCE_ID_EXISTING], inhibitedBy: [] }, }), mockAlertmanagerAlert({ labels: { foo: 'bar', buzz: 'bazz' }, - status: { state: AlertState.Suppressed, silencedBy: ['12345'], inhibitedBy: [] }, + status: { state: AlertState.Suppressed, silencedBy: [MOCK_SILENCE_ID_EXISTING], inhibitedBy: [] }, }), ]) ); diff --git a/public/app/features/alerting/unified/mocks/server/handlers.ts b/public/app/features/alerting/unified/mocks/server/handlers.ts index 78bc9baa86c..7fa5949fdc7 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers.ts @@ -7,16 +7,23 @@ import { alertmanagerChoiceHandler, } from 'app/features/alerting/unified/mocks/alertmanagerApi'; import { datasourceBuildInfoHandler } from 'app/features/alerting/unified/mocks/datasources'; -import { silenceCreateHandler, silencesListHandler } from 'app/features/alerting/unified/mocks/silences'; +import { + silenceCreateHandler, + silenceGetHandler, + silencesListHandler, +} from 'app/features/alerting/unified/mocks/silences'; /** * All mock handlers that are required across Alerting tests */ const allHandlers = [ alertmanagerChoiceHandler(), - silencesListHandler(), - silenceCreateHandler(), alertmanagerAlertsListHandler(), + + silencesListHandler(), + silenceGetHandler(), + silenceCreateHandler(), + datasourceBuildInfoHandler(), ]; diff --git a/public/app/features/alerting/unified/mocks/silences.ts b/public/app/features/alerting/unified/mocks/silences.ts index e23e6ea9ded..75bf988ed28 100644 --- a/public/app/features/alerting/unified/mocks/silences.ts +++ b/public/app/features/alerting/unified/mocks/silences.ts @@ -9,6 +9,17 @@ import { mockSilences } from 'app/features/alerting/unified/mocks'; export const silencesListHandler = (silences = mockSilences) => http.get('/api/alertmanager/:datasourceUid/api/v2/silences', () => HttpResponse.json(silences)); +export const silenceGetHandler = () => + http.get<{ uuid: string }>('/api/alertmanager/:datasourceUid/api/v2/silence/:uuid', ({ params }) => { + const { uuid } = params; + const matchingMockSilence = mockSilences.find((silence) => silence.id === uuid); + if (matchingMockSilence) { + return HttpResponse.json(matchingMockSilence); + } + + return HttpResponse.json({ message: 'silence not found' }, { status: 404 }); + }); + export const silenceCreateHandler = () => http.post('/api/alertmanager/:datasourceUid/api/v2/silences', () => HttpResponse.json({ silenceId: '4bda5b38-7939-4887-9ec2-16323b8e3b4e' }) From aa2f52a2a194e4050e83a8d52aa1e73a2bd3fe32 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 26 Apr 2024 16:15:45 +0100 Subject: [PATCH 181/222] Tidy up error message handling --- .../alerting/unified/components/silences/SilencesTable.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx index 6f9892244ec..bf03af22087 100644 --- a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx +++ b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx @@ -2,7 +2,6 @@ import { css } from '@emotion/css'; import React, { useMemo } from 'react'; import { dateMath, GrafanaTheme2 } from '@grafana/data'; -import { isFetchError } from '@grafana/runtime'; import { CollapsableSection, Icon, Link, LinkButton, useStyles2, Stack, Alert, LoadingPlaceholder } from '@grafana/ui'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { alertSilencesApi } from 'app/features/alerting/unified/api/alertSilencesApi'; @@ -109,8 +108,8 @@ const SilencesTable = ({ alertManagerSourceName }: Props) => { ); } - if (isFetchError(error)) { - const errMessage = error?.message || 'Unknown error.'; + if (error) { + const errMessage = stringifyErrorLike(error) || 'Unknown error.'; return ( {errMessage} From e0ee0b09dbf8234aebb38e234d1d8ff8f1a0e682 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Mon, 29 Apr 2024 13:10:43 +0100 Subject: [PATCH 182/222] Add test to check that a broken alertmanager will handle errors correctly --- .../alerting/unified/Silences.test.tsx | 21 +++++++++++++++++-- .../alerting/unified/mocks/alertmanagerApi.ts | 12 +++++++---- .../alerting/unified/mocks/datasources.ts | 5 +++++ .../alerting/unified/mocks/silences.ts | 8 ++++++- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index 84c35a27d93..6552cdcd590 100644 --- a/public/app/features/alerting/unified/Silences.test.tsx +++ b/public/app/features/alerting/unified/Silences.test.tsx @@ -1,11 +1,15 @@ import React from 'react'; -import { render, waitFor, userEvent } from 'test/test-utils'; +import { render, waitFor, userEvent, screen } from 'test/test-utils'; import { byLabelText, byPlaceholderText, byRole, byTestId, byText } from 'testing-library-selector'; import { dateTime } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { config, locationService, setDataSourceSrv } from '@grafana/runtime'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { + MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER, + MOCK_DATASOURCE_NAME_BROKEN_ALERTMANAGER, +} from 'app/features/alerting/unified/mocks/datasources'; import { waitForServerRequest } from 'app/features/alerting/unified/mocks/server/events'; import { silenceCreateHandler } from 'app/features/alerting/unified/mocks/silences'; import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; @@ -40,6 +44,11 @@ const dataSources = { name: 'Alertmanager', type: DataSourceType.Alertmanager, }), + [MOCK_DATASOURCE_NAME_BROKEN_ALERTMANAGER]: mockDataSource({ + uid: MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER, + name: MOCK_DATASOURCE_NAME_BROKEN_ALERTMANAGER, + type: DataSourceType.Alertmanager, + }), }; const ui = { @@ -100,6 +109,10 @@ const addAdditionalMatcher = async () => { setupMswServer(); +beforeEach(() => { + setupDataSources(dataSources.am, dataSources[MOCK_DATASOURCE_NAME_BROKEN_ALERTMANAGER]); +}); + describe('Silences', () => { beforeAll(resetMocks); afterEach(resetMocks); @@ -185,6 +198,11 @@ describe('Silences', () => { expect(ui.addSilenceButton.query()).not.toBeInTheDocument(); }); + + it('handles error case when broken alertmanager is used', async () => { + renderSilences(`/alerting/silences?alertmanager=${encodeURIComponent(MOCK_DATASOURCE_NAME_BROKEN_ALERTMANAGER)}`); + expect(await screen.findByText(/error loading silences/i)).toBeInTheDocument(); + }); }); describe('Silence create/edit', () => { @@ -194,7 +212,6 @@ describe('Silence create/edit', () => { beforeEach(() => { setUserLogged(true); - setupDataSources(dataSources.am); }); it('Should not render createdBy if user is logged in and has a name', async () => { diff --git a/public/app/features/alerting/unified/mocks/alertmanagerApi.ts b/public/app/features/alerting/unified/mocks/alertmanagerApi.ts index 9c30d8ef699..9083fd30a89 100644 --- a/public/app/features/alerting/unified/mocks/alertmanagerApi.ts +++ b/public/app/features/alerting/unified/mocks/alertmanagerApi.ts @@ -2,6 +2,7 @@ import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/node'; import { MOCK_SILENCE_ID_EXISTING, mockAlertmanagerAlert } from 'app/features/alerting/unified/mocks'; +import { MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER } from 'app/features/alerting/unified/mocks/datasources'; import { AlertmanagerChoice, @@ -47,8 +48,11 @@ export function mockAlertmanagerConfigResponse( } export const alertmanagerAlertsListHandler = () => - http.get('/api/alertmanager/:datasourceUid/api/v2/alerts', () => - HttpResponse.json([ + http.get<{ datasourceUid: string }>('/api/alertmanager/:datasourceUid/api/v2/alerts', ({ params }) => { + if (params.datasourceUid === MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER) { + return HttpResponse.json({ traceId: '' }, { status: 502 }); + } + return HttpResponse.json([ mockAlertmanagerAlert({ labels: { foo: 'bar', buzz: 'bazz' }, status: { state: AlertState.Suppressed, silencedBy: [MOCK_SILENCE_ID_EXISTING], inhibitedBy: [] }, @@ -57,5 +61,5 @@ export const alertmanagerAlertsListHandler = () => labels: { foo: 'bar', buzz: 'bazz' }, status: { state: AlertState.Suppressed, silencedBy: [MOCK_SILENCE_ID_EXISTING], inhibitedBy: [] }, }), - ]) - ); + ]); + }); diff --git a/public/app/features/alerting/unified/mocks/datasources.ts b/public/app/features/alerting/unified/mocks/datasources.ts index 0ff5d12bf65..04f579a7c24 100644 --- a/public/app/features/alerting/unified/mocks/datasources.ts +++ b/public/app/features/alerting/unified/mocks/datasources.ts @@ -3,3 +3,8 @@ import { HttpResponse, http } from 'msw'; // TODO: Add more accurate endpoint responses as tests require export const datasourceBuildInfoHandler = () => http.get('/api/datasources/proxy/uid/:datasourceUid/api/v1/status/buildinfo', () => HttpResponse.json({})); + +/** UID of the alertmanager that is expected to be broken in tests */ +export const MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER = 'FwkfQfEmYlAthB'; +/** Display name of the alertmanager that is expected to be broken in tests */ +export const MOCK_DATASOURCE_NAME_BROKEN_ALERTMANAGER = 'broken alertmanager'; diff --git a/public/app/features/alerting/unified/mocks/silences.ts b/public/app/features/alerting/unified/mocks/silences.ts index 75bf988ed28..8ef9944d83f 100644 --- a/public/app/features/alerting/unified/mocks/silences.ts +++ b/public/app/features/alerting/unified/mocks/silences.ts @@ -1,13 +1,19 @@ import { HttpResponse, http } from 'msw'; import { mockSilences } from 'app/features/alerting/unified/mocks'; +import { MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER } from 'app/features/alerting/unified/mocks/datasources'; ////////////// // Silences // ////////////// export const silencesListHandler = (silences = mockSilences) => - http.get('/api/alertmanager/:datasourceUid/api/v2/silences', () => HttpResponse.json(silences)); + http.get<{ datasourceUid: string }>('/api/alertmanager/:datasourceUid/api/v2/silences', ({ params }) => { + if (params.datasourceUid === MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER) { + return HttpResponse.json({ traceId: '' }, { status: 502 }); + } + return HttpResponse.json(silences); + }); export const silenceGetHandler = () => http.get<{ uuid: string }>('/api/alertmanager/:datasourceUid/api/v2/silence/:uuid', ({ params }) => { From a34d22dcbfaa3b08a7bb161dc344e4af1bcac60c Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Mon, 29 Apr 2024 13:48:34 +0100 Subject: [PATCH 183/222] Include alertmanager param on creation --- public/app/features/alerting/unified/Silences.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/alerting/unified/Silences.test.tsx b/public/app/features/alerting/unified/Silences.test.tsx index 6552cdcd590..e80fca33996 100644 --- a/public/app/features/alerting/unified/Silences.test.tsx +++ b/public/app/features/alerting/unified/Silences.test.tsx @@ -258,7 +258,7 @@ describe('Silence create/edit', () => { 'creates a new silence', async () => { const user = userEvent.setup(); - renderSilences(baseUrlPath); + renderSilences(`${baseUrlPath}?alertmanager=Alertmanager`); expect(await ui.editor.durationField.find()).toBeInTheDocument(); const postRequest = waitForServerRequest(silenceCreateHandler()); From 0a02508415a4453ce7516b0a0aa5a8c9c5ca122a Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Mon, 29 Apr 2024 13:48:54 +0100 Subject: [PATCH 184/222] Add more robust error handling for stringifying API responses --- public/app/features/alerting/unified/utils/misc.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts index 32fc700f0ae..8032c2f4487 100644 --- a/public/app/features/alerting/unified/utils/misc.ts +++ b/public/app/features/alerting/unified/utils/misc.ts @@ -232,7 +232,17 @@ export function isErrorLike(error: unknown): error is Error { export function stringifyErrorLike(error: unknown): string { const fetchError = isFetchError(error); if (fetchError) { - return error.data.message; + if (error.message) { + return error.message; + } + if ('message' in error.data && typeof error.data.message === 'string') { + return error.data.message; + } + if (error.statusText) { + return error.statusText; + } + + return String(error.status) || 'Unknown error'; } return isErrorLike(error) ? error.message : String(error); From 4e6ba433ded19d1a526771ae718ef4716cad8a23 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Mon, 29 Apr 2024 16:55:55 +0200 Subject: [PATCH 185/222] Grafana/ui: Fix traces icon (#86984) --- public/img/icons/custom/gf-traces.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/img/icons/custom/gf-traces.svg b/public/img/icons/custom/gf-traces.svg index a58acc03951..ea83d6e50fb 100644 --- a/public/img/icons/custom/gf-traces.svg +++ b/public/img/icons/custom/gf-traces.svg @@ -1,5 +1,5 @@ - + From 7590f4afe102589fa6d21b55a1f2b9cdc49a3e77 Mon Sep 17 00:00:00 2001 From: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> Date: Mon, 29 Apr 2024 08:23:26 -0700 Subject: [PATCH 186/222] Canvas: Connection original persistence check (#86476) * Canvas: Connection original persistence check * modify current connection state directly instead of copying and needing to call "onChange" --------- Co-authored-by: nmarrs --- .../canvas/components/connections/ConnectionSVG.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx b/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx index b192cb1ca0c..bd7bdf62a1b 100644 --- a/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx +++ b/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx @@ -128,7 +128,7 @@ export const ConnectionSVG = ({ // Render selected connection last, ensuring it is above other connections .sort((_a, b) => (selectedConnection === b && scene.panel.context.instanceState.selectedConnection ? -1 : 0)) .map((v, idx) => { - const { source, target, info, vertices } = v; + const { source, target, info, vertices, index } = v; const sourceRect = source.div?.getBoundingClientRect(); const parent = source.div?.parentElement; const transformScale = scene.scale; @@ -146,6 +146,15 @@ export const ConnectionSVG = ({ yStart = v.sourceOriginal.y; xEnd = v.targetOriginal.x; yEnd = v.targetOriginal.y; + } else if (source.options.connections) { + // If original source or target coordinates are not set for the current connection, set them + if ( + !source.options.connections[index].sourceOriginal || + !source.options.connections[index].targetOriginal + ) { + source.options.connections[index].sourceOriginal = { x: x1, y: y1 }; + source.options.connections[index].targetOriginal = { x: x2, y: y2 }; + } } const midpoint = calculateMidpoint(x1, y1, x2, y2); From 1af2e69625e62cab30d42555e32c9c907cc18cf0 Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 29 Apr 2024 17:23:41 +0200 Subject: [PATCH 187/222] Alerting: Implement DeleteSilence in the forked AM (remote primary) (#85721) --- .../remote/forked_alertmanager_test.go | 20 +++++++++++++------ .../remote_primary_forked_alertmanager.go | 8 +++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/pkg/services/ngalert/remote/forked_alertmanager_test.go b/pkg/services/ngalert/remote/forked_alertmanager_test.go index 80d40da3617..2379e02719d 100644 --- a/pkg/services/ngalert/remote/forked_alertmanager_test.go +++ b/pkg/services/ngalert/remote/forked_alertmanager_test.go @@ -451,15 +451,23 @@ func TestForkedAlertmanager_ModeRemotePrimary(t *testing.T) { }) t.Run("DeleteSilence", func(tt *testing.T) { - // We should delete the silence in the remote Alertmanager. - _, remote, forked := genTestAlertmanagers(tt, modeRemotePrimary) - remote.EXPECT().DeleteSilence(mock.Anything, mock.Anything).Return(nil).Once() - require.NoError(tt, forked.DeleteSilence(ctx, "")) + // We should delete the silence in both Alertmanagers. + testID := "test-id" + internal, remote, forked := genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().DeleteSilence(mock.Anything, testID).Return(nil).Once() + internal.EXPECT().DeleteSilence(mock.Anything, testID).Return(nil).Once() + require.NoError(tt, forked.DeleteSilence(ctx, testID)) // If there's an error in the remote Alertmanager, the error should be returned. _, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) - remote.EXPECT().DeleteSilence(mock.Anything, mock.Anything).Return(expErr).Maybe() - require.ErrorIs(tt, expErr, forked.DeleteSilence(ctx, "")) + remote.EXPECT().DeleteSilence(mock.Anything, testID).Return(expErr).Maybe() + require.ErrorIs(tt, expErr, forked.DeleteSilence(ctx, testID)) + + // An error in the internal Alertmanager should not be returned. + internal, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().DeleteSilence(mock.Anything, testID).Return(nil).Maybe() + internal.EXPECT().DeleteSilence(mock.Anything, testID).Return(nil).Maybe() + require.NoError(tt, forked.DeleteSilence(ctx, testID)) }) t.Run("GetSilence", func(tt *testing.T) { diff --git a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go index 8c2068e8179..1084fff32d5 100644 --- a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go @@ -76,7 +76,13 @@ func (fam *RemotePrimaryForkedAlertmanager) CreateSilence(ctx context.Context, s } func (fam *RemotePrimaryForkedAlertmanager) DeleteSilence(ctx context.Context, id string) error { - return fam.remote.DeleteSilence(ctx, id) + if err := fam.remote.DeleteSilence(ctx, id); err != nil { + return err + } + if err := fam.internal.DeleteSilence(ctx, id); err != nil { + fam.log.Error("Error deleting silence in the internal Alertmanager", "err", err, "id", id) + } + return nil } func (fam *RemotePrimaryForkedAlertmanager) GetSilence(ctx context.Context, id string) (apimodels.GettableSilence, error) { From 8d8f19b84f7b5f70b0e24810f573ee8af19aaef1 Mon Sep 17 00:00:00 2001 From: Julien Duchesne Date: Mon, 29 Apr 2024 11:27:59 -0400 Subject: [PATCH 188/222] Regenerate OpenAPI spec (#87050) Issue: https://github.com/grafana/grafana/issues/86453 The endpoints were documented in enterprise Grafana --- public/api-enterprise-spec.json | 286 +++++++++++++++++++++++++++++- public/api-merged.json | 266 ++++++++++++++++++++++++++++ public/openapi3.json | 299 ++++++++++++++++++++++++++++++++ 3 files changed, 847 insertions(+), 4 deletions(-) diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index e506c1d1ca6..c539729cf4c 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -760,6 +760,155 @@ } } }, + "/datasources/{dataSourceUID}/cache": { + "get": { + "description": "get cache config for a single data source", + "tags": [ + "enterprise" + ], + "operationId": "getDataSourceCacheConfig", + "parameters": [ + { + "type": "string", + "name": "dataSourceUID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "CacheConfigResponse", + "schema": { + "$ref": "#/definitions/CacheConfigResponse" + } + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "post": { + "description": "set cache config for a single data source", + "tags": [ + "enterprise" + ], + "operationId": "setDataSourceCacheConfig", + "parameters": [ + { + "type": "string", + "name": "dataSourceUID", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/CacheConfigSetter" + } + } + ], + "responses": { + "200": { + "description": "CacheConfigResponse", + "schema": { + "$ref": "#/definitions/CacheConfigResponse" + } + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, + "/datasources/{dataSourceUID}/cache/clean": { + "post": { + "description": "clean cache for a single data source", + "tags": [ + "enterprise" + ], + "operationId": "cleanDataSourceCache", + "parameters": [ + { + "type": "string", + "name": "dataSourceUID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "CacheConfigResponse", + "schema": { + "$ref": "#/definitions/CacheConfigResponse" + } + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, + "/datasources/{dataSourceUID}/cache/disable": { + "post": { + "description": "disable cache for a single data source", + "tags": [ + "enterprise" + ], + "operationId": "disableDataSourceCache", + "parameters": [ + { + "type": "string", + "name": "dataSourceUID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "CacheConfigResponse", + "schema": { + "$ref": "#/definitions/CacheConfigResponse" + } + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, + "/datasources/{dataSourceUID}/cache/enable": { + "post": { + "description": "enable cache for a single data source", + "tags": [ + "enterprise" + ], + "operationId": "enableDataSourceCache", + "parameters": [ + { + "type": "string", + "name": "dataSourceUID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "CacheConfigResponse", + "schema": { + "$ref": "#/definitions/CacheConfigResponse" + } + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, "/licensing/check": { "get": { "tags": [ @@ -2502,6 +2651,123 @@ "Value": {} } }, + "CacheConfig": { + "description": "Config defines the internal representation of a cache configuration, including fields not set by the API caller", + "type": "object", + "properties": { + "created": { + "type": "string", + "format": "date-time" + }, + "dataSourceID": { + "description": "Fields that can be set by the API caller - read/write", + "type": "integer", + "format": "int64" + }, + "dataSourceUID": { + "type": "string" + }, + "defaultTTLMs": { + "description": "These are returned by the HTTP API, but are managed internally - read-only\nNote: 'created' and 'updated' are special properties managed automatically by xorm, but we are setting them manually", + "type": "integer", + "format": "int64" + }, + "enabled": { + "type": "boolean" + }, + "ttlQueriesMs": { + "description": "TTL MS, or \"time to live\", is how long a cached item will stay in the cache before it is removed (in milliseconds)", + "type": "integer", + "format": "int64" + }, + "ttlResourcesMs": { + "type": "integer", + "format": "int64" + }, + "updated": { + "type": "string", + "format": "date-time" + }, + "useDefaultTTL": { + "description": "If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini", + "type": "boolean" + } + } + }, + "CacheConfigResponse": { + "type": "object", + "properties": { + "created": { + "type": "string", + "format": "date-time" + }, + "dataSourceID": { + "description": "Fields that can be set by the API caller - read/write", + "type": "integer", + "format": "int64" + }, + "dataSourceUID": { + "type": "string" + }, + "defaultTTLMs": { + "description": "These are returned by the HTTP API, but are managed internally - read-only\nNote: 'created' and 'updated' are special properties managed automatically by xorm, but we are setting them manually", + "type": "integer", + "format": "int64" + }, + "enabled": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "ttlQueriesMs": { + "description": "TTL MS, or \"time to live\", is how long a cached item will stay in the cache before it is removed (in milliseconds)", + "type": "integer", + "format": "int64" + }, + "ttlResourcesMs": { + "type": "integer", + "format": "int64" + }, + "updated": { + "type": "string", + "format": "date-time" + }, + "useDefaultTTL": { + "description": "If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini", + "type": "boolean" + } + } + }, + "CacheConfigSetter": { + "description": "ConfigSetter defines the cache parameters that users can configure per datasource\nThis is only intended to be consumed by the SetCache HTTP Handler", + "type": "object", + "properties": { + "dataSourceID": { + "type": "integer", + "format": "int64" + }, + "dataSourceUID": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "ttlQueriesMs": { + "description": "TTL MS, or \"time to live\", is how long a cached item will stay in the cache before it is removed (in milliseconds)", + "type": "integer", + "format": "int64" + }, + "ttlResourcesMs": { + "type": "integer", + "format": "int64" + }, + "useDefaultTTL": { + "description": "If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini", + "type": "boolean" + } + } + }, "CalculateDiffTarget": { "type": "object", "properties": { @@ -2666,7 +2932,15 @@ "type": "string" } }, + "Policies": { + "description": "Policies contains all policy identifiers included in the certificate.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", + "type": "array", + "items": { + "$ref": "#/definitions/OID" + } + }, "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.", "type": "array", "items": { "$ref": "#/definitions/ObjectIdentifier" @@ -4396,7 +4670,7 @@ "type": "string" }, "IPMask": { - "description": "See type IPNet and func ParseCIDR for details.", + "description": "See type [IPNet] and func [ParseCIDR] for details.", "type": "array", "title": "An IPMask is a bitmask that can be used to manipulate\nIP addresses for IP addressing and routing.", "items": { @@ -4945,7 +5219,7 @@ } }, "Name": { - "description": "Name represents an X.509 distinguished name. This only includes the common\nelements of a DN. Note that Name is only an approximation of the X.509\nstructure. If an accurate representation is needed, asn1.Unmarshal the raw\nsubject or issuer as an RDNSequence.", + "description": "Name represents an X.509 distinguished name. This only includes the common\nelements of a DN. Note that Name is only an approximation of the X.509\nstructure. If an accurate representation is needed, asn1.Unmarshal the raw\nsubject or issuer as an [RDNSequence].", "type": "object", "properties": { "Country": { @@ -5028,6 +5302,10 @@ "format": "int64", "title": "NoticeSeverity is a type for the Severity property of a Notice." }, + "OID": { + "type": "object", + "title": "An OID represents an ASN.1 OBJECT IDENTIFIER." + }, "ObjectIdentifier": { "type": "array", "title": "An ObjectIdentifier represents an ASN.1 OBJECT IDENTIFIER.", @@ -7081,7 +7359,7 @@ } }, "URL": { - "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the EscapedPath method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.", + "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nThe Host field contains the host and port subcomponents of the URL.\nWhen the port is present, it is separated from the host with a colon.\nWhen the host is an IPv6 address, it must be enclosed in square brackets:\n\"[fe80::1]:80\". The [net.JoinHostPort] function combines a host and port\ninto a string suitable for the Host field, adding square brackets to\nthe host when necessary.\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the [URL.EscapedPath] method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.", "type": "object", "title": "A URL represents a parsed URL (technically, a URI reference).", "properties": { @@ -7693,7 +7971,7 @@ } }, "Userinfo": { - "description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a URL. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.", + "description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a [URL]. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.", "type": "object" }, "ValueMapping": { diff --git a/public/api-merged.json b/public/api-merged.json index 2ddc5d10485..fe9e69f24cc 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -4303,6 +4303,155 @@ } } }, + "/datasources/{dataSourceUID}/cache": { + "get": { + "description": "get cache config for a single data source", + "tags": [ + "enterprise" + ], + "operationId": "getDataSourceCacheConfig", + "parameters": [ + { + "type": "string", + "name": "dataSourceUID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "CacheConfigResponse", + "schema": { + "$ref": "#/definitions/CacheConfigResponse" + } + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "post": { + "description": "set cache config for a single data source", + "tags": [ + "enterprise" + ], + "operationId": "setDataSourceCacheConfig", + "parameters": [ + { + "type": "string", + "name": "dataSourceUID", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/CacheConfigSetter" + } + } + ], + "responses": { + "200": { + "description": "CacheConfigResponse", + "schema": { + "$ref": "#/definitions/CacheConfigResponse" + } + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, + "/datasources/{dataSourceUID}/cache/clean": { + "post": { + "description": "clean cache for a single data source", + "tags": [ + "enterprise" + ], + "operationId": "cleanDataSourceCache", + "parameters": [ + { + "type": "string", + "name": "dataSourceUID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "CacheConfigResponse", + "schema": { + "$ref": "#/definitions/CacheConfigResponse" + } + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, + "/datasources/{dataSourceUID}/cache/disable": { + "post": { + "description": "disable cache for a single data source", + "tags": [ + "enterprise" + ], + "operationId": "disableDataSourceCache", + "parameters": [ + { + "type": "string", + "name": "dataSourceUID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "CacheConfigResponse", + "schema": { + "$ref": "#/definitions/CacheConfigResponse" + } + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, + "/datasources/{dataSourceUID}/cache/enable": { + "post": { + "description": "enable cache for a single data source", + "tags": [ + "enterprise" + ], + "operationId": "enableDataSourceCache", + "parameters": [ + { + "type": "string", + "name": "dataSourceUID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "CacheConfigResponse", + "schema": { + "$ref": "#/definitions/CacheConfigResponse" + } + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, "/datasources/{id}": { "get": { "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).\n\nPlease refer to [updated API](#/datasources/getDataSourceByUID) instead", @@ -12480,6 +12629,123 @@ } } }, + "CacheConfig": { + "description": "Config defines the internal representation of a cache configuration, including fields not set by the API caller", + "type": "object", + "properties": { + "created": { + "type": "string", + "format": "date-time" + }, + "dataSourceID": { + "description": "Fields that can be set by the API caller - read/write", + "type": "integer", + "format": "int64" + }, + "dataSourceUID": { + "type": "string" + }, + "defaultTTLMs": { + "description": "These are returned by the HTTP API, but are managed internally - read-only\nNote: 'created' and 'updated' are special properties managed automatically by xorm, but we are setting them manually", + "type": "integer", + "format": "int64" + }, + "enabled": { + "type": "boolean" + }, + "ttlQueriesMs": { + "description": "TTL MS, or \"time to live\", is how long a cached item will stay in the cache before it is removed (in milliseconds)", + "type": "integer", + "format": "int64" + }, + "ttlResourcesMs": { + "type": "integer", + "format": "int64" + }, + "updated": { + "type": "string", + "format": "date-time" + }, + "useDefaultTTL": { + "description": "If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini", + "type": "boolean" + } + } + }, + "CacheConfigResponse": { + "type": "object", + "properties": { + "created": { + "type": "string", + "format": "date-time" + }, + "dataSourceID": { + "description": "Fields that can be set by the API caller - read/write", + "type": "integer", + "format": "int64" + }, + "dataSourceUID": { + "type": "string" + }, + "defaultTTLMs": { + "description": "These are returned by the HTTP API, but are managed internally - read-only\nNote: 'created' and 'updated' are special properties managed automatically by xorm, but we are setting them manually", + "type": "integer", + "format": "int64" + }, + "enabled": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "ttlQueriesMs": { + "description": "TTL MS, or \"time to live\", is how long a cached item will stay in the cache before it is removed (in milliseconds)", + "type": "integer", + "format": "int64" + }, + "ttlResourcesMs": { + "type": "integer", + "format": "int64" + }, + "updated": { + "type": "string", + "format": "date-time" + }, + "useDefaultTTL": { + "description": "If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini", + "type": "boolean" + } + } + }, + "CacheConfigSetter": { + "description": "ConfigSetter defines the cache parameters that users can configure per datasource\nThis is only intended to be consumed by the SetCache HTTP Handler", + "type": "object", + "properties": { + "dataSourceID": { + "type": "integer", + "format": "int64" + }, + "dataSourceUID": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "ttlQueriesMs": { + "description": "TTL MS, or \"time to live\", is how long a cached item will stay in the cache before it is removed (in milliseconds)", + "type": "integer", + "format": "int64" + }, + "ttlResourcesMs": { + "type": "integer", + "format": "int64" + }, + "useDefaultTTL": { + "description": "If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini", + "type": "boolean" + } + } + }, "CalculateDiffTarget": { "type": "object", "properties": { diff --git a/public/openapi3.json b/public/openapi3.json index 5306e9f14bc..420d9bfa204 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -3253,6 +3253,123 @@ "title": "BasicAuth contains basic HTTP authentication credentials.", "type": "object" }, + "CacheConfig": { + "description": "Config defines the internal representation of a cache configuration, including fields not set by the API caller", + "properties": { + "created": { + "format": "date-time", + "type": "string" + }, + "dataSourceID": { + "description": "Fields that can be set by the API caller - read/write", + "format": "int64", + "type": "integer" + }, + "dataSourceUID": { + "type": "string" + }, + "defaultTTLMs": { + "description": "These are returned by the HTTP API, but are managed internally - read-only\nNote: 'created' and 'updated' are special properties managed automatically by xorm, but we are setting them manually", + "format": "int64", + "type": "integer" + }, + "enabled": { + "type": "boolean" + }, + "ttlQueriesMs": { + "description": "TTL MS, or \"time to live\", is how long a cached item will stay in the cache before it is removed (in milliseconds)", + "format": "int64", + "type": "integer" + }, + "ttlResourcesMs": { + "format": "int64", + "type": "integer" + }, + "updated": { + "format": "date-time", + "type": "string" + }, + "useDefaultTTL": { + "description": "If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini", + "type": "boolean" + } + }, + "type": "object" + }, + "CacheConfigResponse": { + "properties": { + "created": { + "format": "date-time", + "type": "string" + }, + "dataSourceID": { + "description": "Fields that can be set by the API caller - read/write", + "format": "int64", + "type": "integer" + }, + "dataSourceUID": { + "type": "string" + }, + "defaultTTLMs": { + "description": "These are returned by the HTTP API, but are managed internally - read-only\nNote: 'created' and 'updated' are special properties managed automatically by xorm, but we are setting them manually", + "format": "int64", + "type": "integer" + }, + "enabled": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "ttlQueriesMs": { + "description": "TTL MS, or \"time to live\", is how long a cached item will stay in the cache before it is removed (in milliseconds)", + "format": "int64", + "type": "integer" + }, + "ttlResourcesMs": { + "format": "int64", + "type": "integer" + }, + "updated": { + "format": "date-time", + "type": "string" + }, + "useDefaultTTL": { + "description": "If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini", + "type": "boolean" + } + }, + "type": "object" + }, + "CacheConfigSetter": { + "description": "ConfigSetter defines the cache parameters that users can configure per datasource\nThis is only intended to be consumed by the SetCache HTTP Handler", + "properties": { + "dataSourceID": { + "format": "int64", + "type": "integer" + }, + "dataSourceUID": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "ttlQueriesMs": { + "description": "TTL MS, or \"time to live\", is how long a cached item will stay in the cache before it is removed (in milliseconds)", + "format": "int64", + "type": "integer" + }, + "ttlResourcesMs": { + "format": "int64", + "type": "integer" + }, + "useDefaultTTL": { + "description": "If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini", + "type": "boolean" + } + }, + "type": "object" + }, "CalculateDiffTarget": { "properties": { "dashboardId": { @@ -16976,6 +17093,188 @@ ] } }, + "/datasources/{dataSourceUID}/cache": { + "get": { + "description": "get cache config for a single data source", + "operationId": "getDataSourceCacheConfig", + "parameters": [ + { + "in": "path", + "name": "dataSourceUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CacheConfigResponse" + } + } + }, + "description": "CacheConfigResponse" + }, + "500": { + "$ref": "#/components/responses/internalServerError" + } + }, + "tags": [ + "enterprise" + ] + }, + "post": { + "description": "set cache config for a single data source", + "operationId": "setDataSourceCacheConfig", + "parameters": [ + { + "in": "path", + "name": "dataSourceUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CacheConfigSetter" + } + } + }, + "required": true, + "x-originalParamName": "body" + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CacheConfigResponse" + } + } + }, + "description": "CacheConfigResponse" + }, + "400": { + "$ref": "#/components/responses/badRequestError" + }, + "500": { + "$ref": "#/components/responses/internalServerError" + } + }, + "tags": [ + "enterprise" + ] + } + }, + "/datasources/{dataSourceUID}/cache/clean": { + "post": { + "description": "clean cache for a single data source", + "operationId": "cleanDataSourceCache", + "parameters": [ + { + "in": "path", + "name": "dataSourceUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CacheConfigResponse" + } + } + }, + "description": "CacheConfigResponse" + }, + "500": { + "$ref": "#/components/responses/internalServerError" + } + }, + "tags": [ + "enterprise" + ] + } + }, + "/datasources/{dataSourceUID}/cache/disable": { + "post": { + "description": "disable cache for a single data source", + "operationId": "disableDataSourceCache", + "parameters": [ + { + "in": "path", + "name": "dataSourceUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CacheConfigResponse" + } + } + }, + "description": "CacheConfigResponse" + }, + "500": { + "$ref": "#/components/responses/internalServerError" + } + }, + "tags": [ + "enterprise" + ] + } + }, + "/datasources/{dataSourceUID}/cache/enable": { + "post": { + "description": "enable cache for a single data source", + "operationId": "enableDataSourceCache", + "parameters": [ + { + "in": "path", + "name": "dataSourceUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CacheConfigResponse" + } + } + }, + "description": "CacheConfigResponse" + }, + "500": { + "$ref": "#/components/responses/internalServerError" + } + }, + "tags": [ + "enterprise" + ] + } + }, "/datasources/{id}": { "delete": { "deprecated": true, From 49fbe970fb9dceca72a7455f5c2a77cf85c4f2a8 Mon Sep 17 00:00:00 2001 From: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> Date: Mon, 29 Apr 2024 09:16:01 -0700 Subject: [PATCH 189/222] Canvas: Fix connection hyperbolic bug (#87002) * Canvas: Connection original persistence check * Canvas: Fix connection hyperbolic bug --- .../panel/canvas/components/connections/ConnectionSVG.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx b/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx index bd7bdf62a1b..40ea21ee2b4 100644 --- a/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx +++ b/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx @@ -259,8 +259,8 @@ export const ConnectionSVG = ({ if (index < vertices.length - 1) { // Not also the last point const nextVertex = vertices[index + 1]; - Xn = nextVertex.x * xDist + x1; - Yn = nextVertex.y * yDist + y1; + Xn = nextVertex.x * xDist + xStart; + Yn = nextVertex.y * yDist + yStart; } // Length of next segment From 36a049912872523eac161a8a8965a03640b31ae2 Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 29 Apr 2024 18:47:25 +0200 Subject: [PATCH 190/222] Alerting: Implement CreateSilence in the forked Alertmanager (remote primary mode) (#85716) --- .../remote/forked_alertmanager_test.go | 26 ++++++++++++++----- .../remote_primary_forked_alertmanager.go | 11 +++++++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/pkg/services/ngalert/remote/forked_alertmanager_test.go b/pkg/services/ngalert/remote/forked_alertmanager_test.go index 2379e02719d..52a04116fbb 100644 --- a/pkg/services/ngalert/remote/forked_alertmanager_test.go +++ b/pkg/services/ngalert/remote/forked_alertmanager_test.go @@ -435,19 +435,31 @@ func TestForkedAlertmanager_ModeRemotePrimary(t *testing.T) { }) t.Run("CreateSilence", func(tt *testing.T) { - // We should create the silence in the remote Alertmanager. - _, remote, forked := genTestAlertmanagers(tt, modeRemotePrimary) - + // We should create the silence in both Alertmanagers using the same uid. + testSilence := &apimodels.PostableSilence{} expID := "test-id" - remote.EXPECT().CreateSilence(mock.Anything, mock.Anything).Return(expID, nil).Once() - id, err := forked.CreateSilence(ctx, nil) + + internal, remote, forked := genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().CreateSilence(mock.Anything, testSilence).Return(expID, nil).Once() + internal.EXPECT().CreateSilence(mock.Anything, testSilence).Return(testSilence.ID, nil).Once() + id, err := forked.CreateSilence(ctx, testSilence) require.NoError(tt, err) + require.Equal(tt, expID, testSilence.ID) require.Equal(tt, expID, id) // If there's an error in the remote Alertmanager, the error should be returned. - remote.EXPECT().CreateSilence(mock.Anything, mock.Anything).Return("", expErr).Maybe() - _, err = forked.CreateSilence(ctx, nil) + _, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().CreateSilence(mock.Anything, mock.Anything).Return("", expErr).Once() + _, err = forked.CreateSilence(ctx, testSilence) require.ErrorIs(tt, expErr, err) + + // An error in the internal Alertmanager should not be returned. + internal, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().CreateSilence(mock.Anything, mock.Anything).Return(expID, nil).Once() + internal.EXPECT().CreateSilence(mock.Anything, mock.Anything).Return("", expErr).Once() + id, err = forked.CreateSilence(ctx, testSilence) + require.NoError(tt, err) + require.Equal(tt, expID, id) }) t.Run("DeleteSilence", func(tt *testing.T) { diff --git a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go index 1084fff32d5..127d9d82e6f 100644 --- a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go @@ -72,7 +72,16 @@ func (fam *RemotePrimaryForkedAlertmanager) GetStatus() apimodels.GettableStatus } func (fam *RemotePrimaryForkedAlertmanager) CreateSilence(ctx context.Context, silence *apimodels.PostableSilence) (string, error) { - return fam.remote.CreateSilence(ctx, silence) + uid, err := fam.remote.CreateSilence(ctx, silence) + if err != nil { + return "", err + } + + silence.ID = uid + if _, err := fam.internal.CreateSilence(ctx, silence); err != nil { + fam.log.Error("Error creating silence in the internal Alertmanager", "err", err, "silence", silence) + } + return uid, nil } func (fam *RemotePrimaryForkedAlertmanager) DeleteSilence(ctx context.Context, id string) error { From b679a32fad63eb1bda5b54c946b1de8921b55c68 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Mon, 29 Apr 2024 18:48:54 +0200 Subject: [PATCH 191/222] Alerting: Allow deleting contact points referenced only by auto-generated policies (#86800) --- .../contact-points/ContactPoints.test.tsx | 40 ++++++++++++++++--- .../contact-points/ContactPoints.tsx | 33 +++++++++------ .../useContactPoints.test.tsx.snap | 34 +++++++++++----- .../contact-points/components/Modals.tsx | 6 ++- .../components/contact-points/utils.ts | 30 ++++++++++---- .../SimplifiedRuleEditor.test.tsx | 3 +- 6 files changed, 109 insertions(+), 37 deletions(-) diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx index 626399aa627..b7fc3ee7c37 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx @@ -21,6 +21,7 @@ import setupMimirFlavoredServer, { MIMIR_DATASOURCE_UID } from './__mocks__/mimi import setupVanillaAlertmanagerFlavoredServer, { VANILLA_ALERTMANAGER_DATASOURCE_UID, } from './__mocks__/vanillaAlertmanagerServer'; +import { RouteReference } from './utils'; /** * There are lots of ways in which we test our pages and components. Here's my opinionated approach to testing them. @@ -224,13 +225,19 @@ describe('contact points', () => { expect(deleteButton).toBeDisabled(); }); - it('should disable delete when contact point is linked to at least one notification policy', async () => { - render( - , + it('should disable delete when contact point is linked to at least one normal notification policy', async () => { + const policies: RouteReference[] = [ { - wrapper, - } - ); + receiver: 'my-contact-point', + route: { + type: 'normal', + }, + }, + ]; + + render(, { + wrapper, + }); expect(screen.getByRole('link', { name: 'is used by 1 notification policy' })).toBeInTheDocument(); @@ -241,6 +248,27 @@ describe('contact points', () => { expect(deleteButton).toBeDisabled(); }); + it('should not disable delete when contact point is linked only to auto-generated notification policy', async () => { + const policies: RouteReference[] = [ + { + receiver: 'my-contact-point', + route: { + type: 'auto-generated', + }, + }, + ]; + + render(, { + wrapper, + }); + + const moreActions = screen.getByRole('button', { name: 'more-actions' }); + await userEvent.click(moreActions); + + const deleteButton = screen.getByRole('menuitem', { name: /delete/i }); + expect(deleteButton).not.toBeDisabled(); + }); + it('should be able to search', async () => { renderWithProvider(); diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx index 9bd140ec305..f8733d208b9 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx @@ -60,7 +60,13 @@ import { useContactPointsWithStatus, useDeleteContactPoint, } from './useContactPoints'; -import { ContactPointWithMetadata, getReceiverDescription, isProvisioned, ReceiverConfigWithMetadata } from './utils'; +import { + ContactPointWithMetadata, + getReceiverDescription, + isProvisioned, + ReceiverConfigWithMetadata, + RouteReference, +} from './utils'; export enum ActiveTab { ContactPoints = 'contact_points', @@ -243,7 +249,7 @@ const ContactPointsList = ({ <> {pageItems.map((contactPoint, index) => { const provisioned = isProvisioned(contactPoint); - const policies = contactPoint.numberOfPolicies; + const policies = contactPoint.policies ?? []; const key = `${contactPoint.name}-${index}`; return ( @@ -304,7 +310,7 @@ interface ContactPointProps { disabled?: boolean; provisioned?: boolean; receivers: ReceiverConfigWithMetadata[]; - policies?: number; + policies?: RouteReference[]; onDelete: (name: string) => void; } @@ -313,7 +319,7 @@ export const ContactPoint = ({ disabled = false, provisioned = false, receivers, - policies = 0, + policies = [], onDelete, }: ContactPointProps) => { const styles = useStyles2(getStyles); @@ -367,12 +373,12 @@ interface ContactPointHeaderProps { name: string; disabled?: boolean; provisioned?: boolean; - policies?: number; + policies?: RouteReference[]; onDelete: (name: string) => void; } const ContactPointHeader = (props: ContactPointHeaderProps) => { - const { name, disabled = false, provisioned = false, policies = 0, onDelete } = props; + const { name, disabled = false, provisioned = false, policies = [], onDelete } = props; const styles = useStyles2(getStyles); const [exportSupported, exportAllowed] = useAlertmanagerAbility(AlertmanagerAction.ExportContactPoint); @@ -381,9 +387,12 @@ const ContactPointHeader = (props: ContactPointHeaderProps) => { const [ExportDrawer, openExportDrawer] = useExportContactPoint(); - const isReferencedByPolicies = policies > 0; + const numberOfPolicies = policies.length; + const isReferencedByAnyPolicy = numberOfPolicies > 0; + const isReferencedByRegularPolicies = policies.some((ref) => ref.route.type !== 'auto-generated'); + const canEdit = editSupported && editAllowed && !provisioned; - const canDelete = deleteSupported && deleteAllowed && !provisioned && policies === 0; + const canDelete = deleteSupported && deleteAllowed && !provisioned && !isReferencedByRegularPolicies; const menuActions: JSX.Element[] = []; @@ -407,7 +416,7 @@ const ContactPointHeader = (props: ContactPointHeaderProps) => { menuActions.push( ( {children} @@ -434,15 +443,15 @@ const ContactPointHeader = (props: ContactPointHeaderProps) => { {name} - {isReferencedByPolicies && ( + {isReferencedByAnyPolicy && ( - is used by {policies} {pluralize('notification policy', policies)} + is used by {numberOfPolicies} {pluralize('notification policy', numberOfPolicies)} )} {provisioned && } - {!isReferencedByPolicies && } + {!isReferencedByAnyPolicy && } = [JSX.Element, (item: T) => void, () => void]; /** @@ -83,7 +85,9 @@ const ErrorModal = ({ isOpen, onDismiss, error }: ErrorModalProps) => ( >

Failed to update your configuration:

- {String(error)} +

+        {stringifyErrorLike(error)}
+      

); diff --git a/public/app/features/alerting/unified/components/contact-points/utils.ts b/public/app/features/alerting/unified/components/contact-points/utils.ts index 6af5766d473..e472fb07f14 100644 --- a/public/app/features/alerting/unified/components/contact-points/utils.ts +++ b/public/app/features/alerting/unified/components/contact-points/utils.ts @@ -1,4 +1,4 @@ -import { countBy, difference, take, trim, upperFirst } from 'lodash'; +import { difference, groupBy, take, trim, upperFirst } from 'lodash'; import { ReactNode } from 'react'; import { config } from '@grafana/runtime'; @@ -99,7 +99,7 @@ export interface ReceiverConfigWithMetadata extends GrafanaManagedReceiverConfig } export interface ContactPointWithMetadata extends GrafanaManagedContactPoint { - numberOfPolicies?: number; // now is optional as we don't have the data from the read-only endpoint + policies?: RouteReference[]; // now is optional as we don't have the data from the read-only endpoint grafana_managed_receiver_configs: ReceiverConfigWithMetadata[]; } @@ -121,7 +121,7 @@ export function enhanceContactPointsWithMetadata( // compute the entire inherited tree before finding what notification policies are using a particular contact point const fullyInheritedTree = computeInheritedTree(alertmanagerConfiguration?.alertmanager_config?.route ?? {}); const usedContactPoints = getUsedContactPoints(fullyInheritedTree); - const usedContactPointsByName = countBy(usedContactPoints); + const usedContactPointsByName = groupBy(usedContactPoints, 'receiver'); const contactPointsList = alertmanagerConfiguration ? alertmanagerConfiguration?.alertmanager_config.receivers ?? [] @@ -133,8 +133,8 @@ export function enhanceContactPointsWithMetadata( return { ...contactPoint, - numberOfPolicies: - alertmanagerConfiguration && usedContactPointsByName && (usedContactPointsByName[contactPoint.name] ?? 0), + policies: + alertmanagerConfiguration && usedContactPointsByName && (usedContactPointsByName[contactPoint.name] ?? []), grafana_managed_receiver_configs: receivers.map((receiver, index) => { const isOnCallReceiver = receiver.type === ReceiverTypes.OnCall; // if we don't have alertmanagerConfiguration we can't get the metadata for oncall receivers, @@ -171,10 +171,26 @@ export function isAutoGeneratedPolicy(route: Route) { ); } -export function getUsedContactPoints(route: Route): string[] { +export interface RouteReference { + receiver: string; + route: { + type: 'auto-generated' | 'normal'; + }; +} + +export function getUsedContactPoints(route: Route): RouteReference[] { const childrenContactPoints = route.routes?.flatMap((route) => getUsedContactPoints(route)) ?? []; + if (route.receiver) { - return [route.receiver, ...childrenContactPoints]; + return [ + { + receiver: route.receiver, + route: { + type: isAutoGeneratedPolicy(route) ? 'auto-generated' : 'normal', + }, + }, + ...childrenContactPoints, + ]; } return childrenContactPoints; diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx index e449a25f8ea..6535e123522 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx @@ -263,6 +263,7 @@ describe('Can create a new grafana managed alert unsing simplified routing', () expect(mocks.api.setRulerRuleGroup).not.toHaveBeenCalled(); }); }); + it('can create new grafana managed alert when using simplified routing and selecting a contact point', async () => { const contactPointsAvailable: ContactPointWithMetadata[] = [ { @@ -279,7 +280,7 @@ describe('Can create a new grafana managed alert unsing simplified routing', () settings: {}, }, ], - numberOfPolicies: 0, + policies: [], }, ]; mocks.useContactPointsWithStatus.mockReturnValue({ From 7b392d40a019bfbc51d63431ca3e748ad7e4c43e Mon Sep 17 00:00:00 2001 From: Isabella Siu Date: Mon, 29 Apr 2024 13:07:45 -0400 Subject: [PATCH 192/222] Auth: Sign sigV4 request after adding headers (#87063) --- .../httpclientprovider/http_client_provider.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/infra/httpclient/httpclientprovider/http_client_provider.go b/pkg/infra/httpclient/httpclientprovider/http_client_provider.go index 5447999a1fd..13ef9674c1c 100644 --- a/pkg/infra/httpclient/httpclientprovider/http_client_provider.go +++ b/pkg/infra/httpclient/httpclientprovider/http_client_provider.go @@ -32,10 +32,6 @@ func New(cfg *setting.Cfg, validator validations.PluginRequestValidator, tracer RedirectLimitMiddleware(validator), } - if cfg.SigV4AuthEnabled { - middlewares = append(middlewares, awssdk.SigV4Middleware(cfg.SigV4VerboseLogging)) - } - if httpLoggingEnabled(cfg.PluginSettings) { middlewares = append(middlewares, HTTPLoggerMiddleware(cfg.PluginSettings)) } @@ -44,6 +40,11 @@ func New(cfg *setting.Cfg, validator validations.PluginRequestValidator, tracer middlewares = append(middlewares, GrafanaRequestIDHeaderMiddleware(cfg, logger)) } + // SigV4 signing should be performed after all headers are added + if cfg.SigV4AuthEnabled { + middlewares = append(middlewares, awssdk.SigV4Middleware(cfg.SigV4VerboseLogging)) + } + setDefaultTimeoutOptions(cfg) return newProviderFunc(sdkhttpclient.ProviderOptions{ From 3845033308f1914a685ac3c7514352b834a80319 Mon Sep 17 00:00:00 2001 From: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com> Date: Mon, 29 Apr 2024 12:09:47 -0500 Subject: [PATCH 193/222] Docs: Update Explore Metrics doc based on feedback (#87062) * changed from private preview to public preview * commented out pivot to logs and traces --- docs/sources/explore/explore-metrics.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/sources/explore/explore-metrics.md b/docs/sources/explore/explore-metrics.md index 7433d18c455..6ad02a1d824 100644 --- a/docs/sources/explore/explore-metrics.md +++ b/docs/sources/explore/explore-metrics.md @@ -14,7 +14,9 @@ weight: 200 Grafana Explore Metrics is a query-less experience for browsing **Prometheus-compatible** metrics. Quickly find related metrics with just a few simple clicks, without needing to write PromQL queries to retrieve metrics. -{{< docs/public-preview product="Explore Metrics" >}} +{{% admonition type="caution" %}} +Explore Metrics is currently in [public preview](/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available. +{{% /admonition %}} With Explore Metrics, you can: @@ -23,7 +25,7 @@ With Explore Metrics, you can: - surface other metrics relevant to the current metric - “explore in a drawer” - expand a drawer over a dashboard with more content so you don’t lose your place - view a history of user steps when navigating through metrics and their filters -- easily pivot to other related telemetry, including logs or traces + You can access Explore Metrics either as a standalone experience or as part of Grafana dashboards. From 70ff229bed758374de1faecbb058cf9dcb9ee0d7 Mon Sep 17 00:00:00 2001 From: William Wernert Date: Mon, 29 Apr 2024 13:13:29 -0400 Subject: [PATCH 194/222] Alerting: Use expected field name for receiver in HCL export (#87065) * Use expected field name for receiver in hcl Terraform provider expects `contact_point` instead of `receiver` in notification settings on a rule. --- pkg/services/ngalert/api/api_provisioning_test.go | 2 +- .../ngalert/api/test-data/post-rulegroup-101-export.hcl | 2 +- .../api/tooling/definitions/provisioning_alert_rules.go | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index e2db47386db..0d62ee98ed3 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -694,7 +694,7 @@ func TestProvisioningApi(t *testing.T) { is_paused = false notification_settings { - receiver = "Test-Receiver" + contact_point = "Test-Receiver" group_by = ["alertname", "grafana_folder", "test"] group_wait = "1s" group_interval = "5s" diff --git a/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl b/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl index 2cfb1ba5b0e..19810a71554 100644 --- a/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl +++ b/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl @@ -79,7 +79,7 @@ resource "grafana_rule_group" "rule_group_0000" { is_paused = false notification_settings { - receiver = "Test-Receiver" + contact_point = "Test-Receiver" group_by = ["alertname", "grafana_folder", "test"] group_wait = "1s" group_interval = "5s" diff --git a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go index bd8e1548861..0ebd6384a73 100644 --- a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go +++ b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go @@ -292,7 +292,8 @@ type RelativeTimeRangeExport struct { // AlertRuleNotificationSettingsExport is the provisioned export of models.NotificationSettings. type AlertRuleNotificationSettingsExport struct { - Receiver string `yaml:"receiver,omitempty" json:"receiver,omitempty" hcl:"receiver"` + // Terraform provider uses `contact_point`, so export the field with that name in HCL. + Receiver string `yaml:"receiver,omitempty" json:"receiver,omitempty" hcl:"contact_point"` GroupBy []string `yaml:"group_by,omitempty" json:"group_by,omitempty" hcl:"group_by"` GroupWait *string `yaml:"group_wait,omitempty" json:"group_wait,omitempty" hcl:"group_wait,optional"` From 16395f9f23519987ef71c499ea8bc7e24c3e79df Mon Sep 17 00:00:00 2001 From: Andrej Ocenas Date: Mon, 29 Apr 2024 20:41:40 +0200 Subject: [PATCH 195/222] Pyroscope: Add adhoc filters support (#85601) * Add adhoc filters support * Add tests * refactor tests * Add comment * Removed empty param docs --- packages/grafana-data/src/types/time.ts | 17 +++++ .../datasource.test.ts | 67 ++++++++++++------- .../datasource.ts | 65 ++++++++++-------- .../grafana-pyroscope-datasource/utils.ts | 57 ++++++++++++++-- 4 files changed, 147 insertions(+), 59 deletions(-) diff --git a/packages/grafana-data/src/types/time.ts b/packages/grafana-data/src/types/time.ts index edc92429ca6..f3eeefac9ce 100644 --- a/packages/grafana-data/src/types/time.ts +++ b/packages/grafana-data/src/types/time.ts @@ -86,3 +86,20 @@ export function getDefaultRelativeTimeRange(): RelativeTimeRange { to: 0, }; } + +/** + * Simple helper to quickly create a TimeRange object either from string representations of a dateTime or directly + * DateTime objects. + */ +export function makeTimeRange(from: DateTime | string, to: DateTime | string): TimeRange { + const fromDateTime = typeof from === 'string' ? dateTime(from) : from; + const toDateTime = typeof to === 'string' ? dateTime(to) : to; + return { + from: fromDateTime, + to: toDateTime, + raw: { + from: fromDateTime, + to: toDateTime, + }, + }; +} diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.test.ts b/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.test.ts index 6c4720c0600..9d1c9246dba 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.test.ts +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.test.ts @@ -5,27 +5,14 @@ import { PluginMetaInfo, PluginType, DataSourceJsonData, + makeTimeRange, } from '@grafana/data'; -import { setPluginExtensionsHook, getBackendSrv, setBackendSrv, getTemplateSrv } from '@grafana/runtime'; +import { setPluginExtensionsHook, getBackendSrv, setBackendSrv, TemplateSrv } from '@grafana/runtime'; import { defaultPyroscopeQueryType } from './dataquery.gen'; import { normalizeQuery, PyroscopeDataSource } from './datasource'; import { Query } from './types'; -jest.mock('@grafana/runtime', () => { - const actual = jest.requireActual('@grafana/runtime'); - return { - ...actual, - getTemplateSrv: () => { - return { - replace: (query: string): string => { - return query.replace(/\$var/g, 'interpolated'); - }, - }; - }, - }; -}); - /** The datasource QueryEditor fetches datasource settings to send to the extension's `configure` method */ export function mockFetchPyroscopeDatasourceSettings( datasourceSettings?: Partial> @@ -46,16 +33,21 @@ export function mockFetchPyroscopeDatasourceSettings( }); } -describe('Pyroscope data source', () => { - let ds: PyroscopeDataSource; - beforeEach(() => { - mockFetchPyroscopeDatasourceSettings(); - setPluginExtensionsHook(() => ({ extensions: [], isLoading: false })); // No extensions - ds = new PyroscopeDataSource(defaultSettings); - }); +function setupDatasource() { + mockFetchPyroscopeDatasourceSettings(); + setPluginExtensionsHook(() => ({ extensions: [], isLoading: false })); // No extensions + const templateSrv = { + replace: (query: string): string => { + return query.replace(/\$var/g, 'interpolated'); + }, + } as unknown as TemplateSrv; + return new PyroscopeDataSource(defaultSettings, templateSrv); +} +describe('Pyroscope data source', () => { describe('importing queries', () => { it('keeps all labels and values', async () => { + const ds = setupDatasource(); const queries = await ds.importFromAbstractQueries([ { refId: 'A', @@ -71,6 +63,7 @@ describe('Pyroscope data source', () => { describe('exporting queries', () => { it('keeps all labels and values', async () => { + const ds = setupDatasource(); const queries = await ds.exportToAbstractQueries([ { refId: 'A', @@ -93,10 +86,8 @@ describe('Pyroscope data source', () => { }); describe('applyTemplateVariables', () => { - const templateSrv = getTemplateSrv(); - it('should not update labelSelector if there are no template variables', () => { - ds = new PyroscopeDataSource(defaultSettings, templateSrv); + const ds = setupDatasource(); const query = ds.applyTemplateVariables(defaultQuery({ labelSelector: `no var`, profileTypeId: 'no var' }), {}); expect(query).toMatchObject({ labelSelector: `no var`, @@ -105,7 +96,7 @@ describe('Pyroscope data source', () => { }); it('should update labelSelector if there are template variables', () => { - ds = new PyroscopeDataSource(defaultSettings, templateSrv); + const ds = setupDatasource(); const query = ds.applyTemplateVariables( defaultQuery({ labelSelector: `{$var="$var"}`, profileTypeId: '$var' }), {} @@ -113,6 +104,30 @@ describe('Pyroscope data source', () => { expect(query).toMatchObject({ labelSelector: `{interpolated="interpolated"}`, profileTypeId: 'interpolated' }); }); }); + + it('implements ad hoc variable support for keys', async () => { + const ds = setupDatasource(); + jest.spyOn(ds, 'getResource').mockImplementationOnce(async (cb) => ['foo', 'bar', 'baz']); + const keys = await ds.getTagKeys({ + filters: [], + timeRange: makeTimeRange('2024-01-01T00:00:00', '2024-01-01T01:00:00'), + }); + expect(keys).toEqual(['foo', 'bar', 'baz'].map((v) => ({ text: v }))); + }); + + it('implements ad hoc variable support for values', async () => { + const ds = setupDatasource(); + jest.spyOn(ds, 'getResource').mockImplementationOnce(async (path, params) => { + expect(params?.label).toEqual('foo'); + return ['xyz', 'tuv']; + }); + const keys = await ds.getTagValues({ + key: 'foo', + filters: [], + timeRange: makeTimeRange('2024-01-01T00:00:00', '2024-01-01T01:00:00'), + }); + expect(keys).toEqual(['xyz', 'tuv'].map((v) => ({ text: v }))); + }); }); describe('normalizeQuery', () => { diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.ts b/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.ts index 52d50b6995a..a748d560269 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.ts +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.ts @@ -1,12 +1,16 @@ -import Prism, { Grammar } from 'prismjs'; +import Prism from 'prismjs'; import { Observable, of } from 'rxjs'; import { AbstractQuery, + AdHocVariableFilter, CoreApp, DataQueryRequest, DataQueryResponse, + DataSourceGetTagKeysOptions, + DataSourceGetTagValuesOptions, DataSourceInstanceSettings, + MetricFindValue, ScopedVars, } from '@grafana/data'; import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime'; @@ -14,7 +18,7 @@ import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/run import { VariableSupport } from './VariableSupport'; import { defaultGrafanaPyroscopeDataQuery, defaultPyroscopeQueryType } from './dataquery.gen'; import { PyroscopeDataSourceOptions, Query, ProfileTypeMessage } from './types'; -import { extractLabelMatchers, toPromLikeExpr } from './utils'; +import { addLabelToQuery, extractLabelMatchers, grammar, toPromLikeExpr } from './utils'; export class PyroscopeDataSource extends DataSourceWithBackend { constructor( @@ -71,10 +75,37 @@ export class PyroscopeDataSource extends DataSourceWithBackend): Promise { + const data = this.adhocFilterData(options); + const labels = await this.getLabelNames(data.query, data.from, data.to); + return labels.map((label) => ({ text: label })); + } + + // By implementing getTagKeys and getTagValues we add ad-hoc filters functionality + async getTagValues(options: DataSourceGetTagValuesOptions): Promise { + const data = this.adhocFilterData(options); + const labels = await this.getLabelValues(data.query, options.key, data.from, data.to); + return labels.map((label) => ({ text: label })); + } + + private adhocFilterData(options: DataSourceGetTagKeysOptions | DataSourceGetTagValuesOptions) { + const from = options.timeRange?.from.valueOf() ?? Date.now() - 1000 * 60 * 60 * 24; + const to = options.timeRange?.to.valueOf() ?? Date.now(); + const query = '{' + options.filters.map((f) => `${f.key}${f.operator}"${f.value}"`).join(',') + '}'; + return { from, to, query }; + } + + applyTemplateVariables(query: Query, scopedVars: ScopedVars, filters?: AdHocVariableFilter[]): Query { + let labelSelector = this.templateSrv.replace(query.labelSelector ?? '', scopedVars); + if (filters && labelSelector) { + for (const filter of filters) { + labelSelector = addLabelToQuery(labelSelector, filter.key, filter.value, filter.operator); + } + } return { ...query, - labelSelector: this.templateSrv.replace(query.labelSelector ?? '', scopedVars), + labelSelector, profileTypeId: this.templateSrv.replace(query.profileTypeId ?? '', scopedVars), }; } @@ -86,7 +117,7 @@ export class PyroscopeDataSource extends DataSourceWithBackend): AbstractLabelMatcher[] { const labelMatchers: AbstractLabelMatcher[] = []; @@ -47,8 +47,8 @@ export function extractLabelMatchers(tokens: Array): AbstractLab return labelMatchers; } -export function toPromLikeExpr(labelBasedQuery: AbstractQuery): string { - const expr = labelBasedQuery.labelMatchers +export function toPromLikeExpr(labelMatchers: AbstractLabelMatcher[]): string { + const expr = labelMatchers .map((selector: AbstractLabelMatcher) => { const operator = ToPromLikeMap[selector.operator]; if (operator) { @@ -82,3 +82,52 @@ const ToPromLikeMap: Record = invert(FromPromLike AbstractLabelOperator, string >; + +/** + * Modifies query, adding a new label=value pair to it while preserving other parts of the query. This operates on a + * string representation of the query which needs to be parsed and then rendered to string again. + */ +export function addLabelToQuery(query: string, key: string, value: string | number, operator = '='): string { + if (!key || !value) { + throw new Error('Need label to add to query.'); + } + + const tokens = Prism.tokenize(query, grammar); + let labels = extractLabelMatchers(tokens); + + // If we already have such label in the query, remove it and we will replace it. If we didn't we would end up + // with query like `a=b,a=c` which won't return anything. Replacing also seems more meaningful here than just + // ignoring the filter and keeping the old value. + labels = labels.filter((l) => l.name !== key); + labels.push({ + name: key, + value: value.toString(), + operator: FromPromLikeMap[operator] ?? AbstractLabelOperator.Equal, + }); + + return toPromLikeExpr(labels); +} + +export const grammar: Grammar = { + 'context-labels': { + pattern: /\{[^}]*(?=}?)/, + greedy: true, + inside: { + comment: { + pattern: /#.*/, + }, + 'label-key': { + pattern: /[a-zA-Z_]\w*(?=\s*(=|!=|=~|!~))/, + alias: 'attr-name', + greedy: true, + }, + 'label-value': { + pattern: /"(?:\\.|[^\\"])*"/, + greedy: true, + alias: 'attr-value', + }, + punctuation: /[{]/, + }, + }, + punctuation: /[{}(),.]/, +}; From 7505af2886fd08769a2d3c0d966277d770d97270 Mon Sep 17 00:00:00 2001 From: Scott Lepper Date: Mon, 29 Apr 2024 19:46:19 +0100 Subject: [PATCH 196/222] Chore: Update go-duck dependency to v0.0.18 (#87073) * Chore: Update go-duck dependency to v0.0.18 --- go.mod | 2 +- go.sum | 4 ++-- go.work.sum | 13 +++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 623c1e4ffa8..0eb181b83cf 100644 --- a/go.mod +++ b/go.mod @@ -146,7 +146,7 @@ require ( github.com/redis/go-redis/v9 v9.1.0 // @grafana/alerting-squad-backend github.com/robfig/cron/v3 v3.0.1 // @grafana/grafana-backend-group github.com/russellhaering/goxmldsig v1.4.0 // @grafana/grafana-backend-group - github.com/scottlepp/go-duck v0.0.15 // @grafana/grafana-app-platform-squad + github.com/scottlepp/go-duck v0.0.19 // @grafana/grafana-app-platform-squad github.com/spf13/cobra v1.8.0 // @grafana/grafana-app-platform-squad github.com/spf13/pflag v1.0.5 // @grafana-app-platform-squad github.com/spyzhov/ajson v0.9.0 // @grafana/grafana-app-platform-squad diff --git a/go.sum b/go.sum index 71cb467b0e0..69a180ea839 100644 --- a/go.sum +++ b/go.sum @@ -2919,8 +2919,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21 h1:yWfiTPwYxB0l5fGMhl/G+liULugVIHD9AU77iNLrURQ= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.21/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= -github.com/scottlepp/go-duck v0.0.15 h1:qrSF3pXlXAA4a7uxAfLYajqXLkeBjv8iW1wPdSfkMj0= -github.com/scottlepp/go-duck v0.0.15/go.mod h1:GL+hHuKdueJRrFCduwBc7A7TQk+Tetc5BPXPVtduihY= +github.com/scottlepp/go-duck v0.0.19 h1:SjO0HF+xe6TN9agMram+CG8+NWKgGMSj8LfqRm0JvpA= +github.com/scottlepp/go-duck v0.0.19/go.mod h1:GL+hHuKdueJRrFCduwBc7A7TQk+Tetc5BPXPVtduihY= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= diff --git a/go.work.sum b/go.work.sum index 2fa7d95e67a..51b558d5e4a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,3 +1,4 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1 h1:vp9EaPFSb75qe/793x58yE5fY1IJ/gdxb/kcDUzavtI= buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1/go.mod h1:YDq2B5X5BChU0lxAG5MxHpDb8mx1fv9OGtF2mwOe7hY= buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.28.1-20221127060915-a1ecdc58eccd.4 h1:z3Xc9n8yZ5k/Xr4ZTuff76TAYP20dWy7ZBV4cGIpbkM= @@ -419,6 +420,7 @@ github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjH github.com/alecthomas/kong v0.2.11 h1:RKeJXXWfg9N47RYfMm0+igkxBCTF4bzbneAxaqid0c4= github.com/alecthomas/kong v0.2.11/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= github.com/alecthomas/participle/v2 v2.1.0 h1:z7dElHRrOEEq45F2TG5cbQihMtNTv8vwldytDj7Wrz4= +github.com/alecthomas/participle/v2 v2.1.0/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= @@ -450,6 +452,7 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= +github.com/bufbuild/protovalidate-go v0.2.1/go.mod h1:e7XXDtlxj5vlEyAgsrxpzayp4cEMKCSSb8ZCkin+MVA= github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw= github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw= @@ -606,6 +609,7 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54= +github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4 h1:vF83LI8tAakwEwvWZtrIEx7pOySacl2TOxx6eXk4ePo= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/gofiber/fiber/v2 v2.46.0 h1:wkkWotblsGVlLjXj2dpgKQAYHtXumsK/HyFugQM68Ns= @@ -651,6 +655,7 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWet github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o= github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -875,6 +880,8 @@ github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1Avp github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/scottlepp/go-duck v0.0.19 h1:SjO0HF+xe6TN9agMram+CG8+NWKgGMSj8LfqRm0JvpA= +github.com/scottlepp/go-duck v0.0.19/go.mod h1:GL+hHuKdueJRrFCduwBc7A7TQk+Tetc5BPXPVtduihY= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e h1:uO75wNGioszjmIzcY/tvdDYKRLVvzggtAmmJkn9j4GQ= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZFQ8e24NYaBGIlnO2WGCAi67re4HHuOm0sftE/M= github.com/segmentio/parquet-go v0.0.0-20230427215636-d483faba23a5 h1:7CWCjaHrXSUCHrRhIARMGDVKdB82tnPAQMmANeflKOw= @@ -899,10 +906,15 @@ github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e h1:mOtuXaRAbVZsxAH github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/substrait-io/substrait-go v0.4.2 h1:buDnjsb3qAqTaNbOR7VKmNgXf4lYQxWEcnSGUWBtmN8= +github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg= github.com/tdewolff/minify/v2 v2.12.9 h1:dvn5MtmuQ/DFMwqf5j8QhEVpPX6fi3WGImhv8RUB4zA= github.com/tdewolff/minify/v2 v2.12.9/go.mod h1:qOqdlDfL+7v0/fyymB+OP497nIxJYSvX4MQWA8OoiXU= github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA= github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= @@ -1059,6 +1071,7 @@ gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6 h1:WN8Lymy+dCTDHgn4vhUSNIB6U+0sDiv/c9Zdr0UeAnI= k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6/go.mod h1:l0ukbPS0lwFxOzSq5ZqjutzF+5IL2TLp495PswRPSZk= +k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8= k8s.io/kms v0.29.0/go.mod h1:mB0f9HLxRXeXUfHfn1A7rpwOlzXI1gIWu86z6buNoYA= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= From e3719471d5daccbad3fcc736144c942f2a4cd4cb Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:02:38 -0600 Subject: [PATCH 197/222] Heatmap: Fix histogram highlighted series (#86955) --- .../plugins/panel/heatmap/renderHistogram.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/public/app/plugins/panel/heatmap/renderHistogram.tsx b/public/app/plugins/panel/heatmap/renderHistogram.tsx index 6169151089c..14278fcfcc2 100644 --- a/public/app/plugins/panel/heatmap/renderHistogram.tsx +++ b/public/app/plugins/panel/heatmap/renderHistogram.tsx @@ -10,11 +10,12 @@ export function renderHistogram( let histCtx = can.current?.getContext('2d'); if (histCtx != null) { + const barsGap = 1; let fromIdx = index; - while (xVals[fromIdx--] === xVals[index]) {} - - fromIdx++; + while (xVals[fromIdx - 1] === xVals[index]) { + fromIdx--; + } let toIdx = fromIdx + yBucketCount; @@ -37,16 +38,14 @@ export function renderHistogram( if (c > 0) { let pctY = c / maxCount; - let pctX = j / (yBucketCount + 1); + let pctX = j / yBucketCount; let p = i === index ? pHov : pRest; - p.rect( - Math.round(histCanWidth * pctX), - Math.round(histCanHeight * (1 - pctY)), - Math.round(histCanWidth / yBucketCount), - Math.round(histCanHeight * pctY) - ); + const xCoord = histCanWidth * pctX + barsGap; + const width = histCanWidth / yBucketCount - barsGap; + + p.rect(xCoord, Math.round(histCanHeight * (1 - pctY)), width, Math.round(histCanHeight * pctY)); } i++; From 8bb9b06e482f032f13316c0502e116f7d7e545d8 Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:34:10 +0100 Subject: [PATCH 198/222] Chore: Rewrite grafana-sql css using object styles (#87052) --- .betterer.results | 16 +----- .../query-editor-raw/QueryToolbox.tsx | 50 +++++++++---------- .../query-editor-raw/QueryValidator.tsx | 22 ++++---- .../components/query-editor-raw/RawEditor.tsx | 16 +++--- 4 files changed, 45 insertions(+), 59 deletions(-) diff --git a/.betterer.results b/.betterer.results index 7bd18516f5a..2c05174492b 100644 --- a/.betterer.results +++ b/.betterer.results @@ -723,21 +723,7 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "6"] ], "packages/grafana-sql/src/components/query-editor-raw/QueryToolbox.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"] - ], - "packages/grafana-sql/src/components/query-editor-raw/QueryValidator.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] - ], - "packages/grafana-sql/src/components/query-editor-raw/RawEditor.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], "packages/grafana-sql/src/components/visual-query-builder/AwesomeQueryBuilder.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] diff --git a/packages/grafana-sql/src/components/query-editor-raw/QueryToolbox.tsx b/packages/grafana-sql/src/components/query-editor-raw/QueryToolbox.tsx index a8817e102f6..5798b65b567 100644 --- a/packages/grafana-sql/src/components/query-editor-raw/QueryToolbox.tsx +++ b/packages/grafana-sql/src/components/query-editor-raw/QueryToolbox.tsx @@ -20,31 +20,31 @@ export function QueryToolbox({ showTools, onFormatCode, onExpand, isExpanded, .. const styles = useMemo(() => { return { - container: css` - border: 1px solid ${theme.colors.border.medium}; - border-top: none; - padding: ${theme.spacing(0.5, 0.5, 0.5, 0.5)}; - display: flex; - flex-grow: 1; - justify-content: space-between; - font-size: ${theme.typography.bodySmall.fontSize}; - `, - error: css` - color: ${theme.colors.error.text}; - font-size: ${theme.typography.bodySmall.fontSize}; - font-family: ${theme.typography.fontFamilyMonospace}; - `, - valid: css` - color: ${theme.colors.success.text}; - `, - info: css` - color: ${theme.colors.text.secondary}; - `, - hint: css` - color: ${theme.colors.text.disabled}; - white-space: nowrap; - cursor: help; - `, + container: css({ + border: `1px solid ${theme.colors.border.medium}`, + borderTop: 'none', + padding: theme.spacing(0.5, 0.5, 0.5, 0.5), + display: 'flex', + flexGrow: 1, + justifyContent: 'space-between', + fontSize: theme.typography.bodySmall.fontSize, + }), + error: css({ + color: theme.colors.error.text, + fontSize: theme.typography.bodySmall.fontSize, + fontFamily: theme.typography.fontFamilyMonospace, + }), + valid: css({ + color: theme.colors.success.text, + }), + info: css({ + color: theme.colors.text.secondary, + }), + hint: css({ + color: theme.colors.text.disabled, + whiteSpace: 'nowrap', + cursor: 'help', + }), }; }, [theme]); diff --git a/packages/grafana-sql/src/components/query-editor-raw/QueryValidator.tsx b/packages/grafana-sql/src/components/query-editor-raw/QueryValidator.tsx index 0cbad6702a8..c787229b9d9 100644 --- a/packages/grafana-sql/src/components/query-editor-raw/QueryValidator.tsx +++ b/packages/grafana-sql/src/components/query-editor-raw/QueryValidator.tsx @@ -22,17 +22,17 @@ export function QueryValidator({ db, query, onValidate, range }: QueryValidatorP const styles = useMemo(() => { return { - error: css` - color: ${theme.colors.error.text}; - font-size: ${theme.typography.bodySmall.fontSize}; - font-family: ${theme.typography.fontFamilyMonospace}; - `, - valid: css` - color: ${theme.colors.success.text}; - `, - info: css` - color: ${theme.colors.text.secondary}; - `, + error: css({ + color: theme.colors.error.text, + fontSize: theme.typography.bodySmall.fontSize, + fontFamily: theme.typography.fontFamilyMonospace, + }), + valid: css({ + color: theme.colors.success.text, + }), + info: css({ + color: theme.colors.text.secondary, + }), }; }, [theme]); diff --git a/packages/grafana-sql/src/components/query-editor-raw/RawEditor.tsx b/packages/grafana-sql/src/components/query-editor-raw/RawEditor.tsx index 19215827b74..0c6883adaa9 100644 --- a/packages/grafana-sql/src/components/query-editor-raw/RawEditor.tsx +++ b/packages/grafana-sql/src/components/query-editor-raw/RawEditor.tsx @@ -114,13 +114,13 @@ export function RawEditor({ db, query, onChange, onRunQuery, onValidate, queryTo function getStyles(theme: GrafanaTheme2) { return { - modal: css` - width: 95vw; - height: 95vh; - `, - modalContent: css` - height: 100%; - padding-top: 0; - `, + modal: css({ + width: '95vw', + height: '95vh', + }), + modalContent: css({ + height: '100%', + paddingTop: 0, + }), }; } From 052082a9273ed995b3862b22fa39b9bfff81a4be Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Mon, 29 Apr 2024 21:52:15 -0400 Subject: [PATCH 199/222] Alerting: Refactor Alert Rule Generators (#86813) --- .../loki/historian_store_test.go | 46 +- .../ngalert/accesscontrol/rules_test.go | 42 +- .../ngalert/api/api_prometheus_test.go | 60 ++- .../ngalert/api/api_ruler_export_test.go | 44 +- pkg/services/ngalert/api/api_ruler_test.go | 121 ++--- pkg/services/ngalert/api/api_testing_test.go | 16 +- pkg/services/ngalert/api/util_test.go | 5 +- .../ngalert/backtesting/engine_test.go | 3 +- .../ngalert/models/alert_rule_test.go | 69 +-- pkg/services/ngalert/models/testing.go | 480 ++++++++++++------ pkg/services/ngalert/ngalert_test.go | 3 +- .../provisioning/accesscontrol_test.go | 2 +- .../ngalert/provisioning/alert_rules_test.go | 46 +- .../ngalert/schedule/alert_rule_test.go | 37 +- pkg/services/ngalert/schedule/jitter_test.go | 27 +- .../schedule/loaded_metrics_reader_test.go | 2 +- .../ngalert/schedule/registry_bench_test.go | 7 +- .../ngalert/schedule/registry_test.go | 7 +- .../ngalert/schedule/schedule_unit_test.go | 10 +- .../ngalert/state/cache_bench_test.go | 4 +- pkg/services/ngalert/state/cache_test.go | 3 +- .../state/historian/annotation_test.go | 15 +- .../ngalert/state/manager_private_test.go | 52 +- pkg/services/ngalert/state/manager_test.go | 58 +-- pkg/services/ngalert/state/state_test.go | 3 +- pkg/services/ngalert/store/alert_rule_test.go | 117 ++--- pkg/services/ngalert/store/deltas_test.go | 108 ++-- 27 files changed, 739 insertions(+), 648 deletions(-) diff --git a/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go b/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go index a364e9c48bc..5202952cda9 100644 --- a/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go +++ b/pkg/services/annotations/annotationsimpl/loki/historian_store_test.go @@ -7,10 +7,11 @@ import ( "math/rand" "net/url" "strconv" - "sync" "testing" "time" + "github.com/prometheus/client_golang/prometheus" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" @@ -28,7 +29,6 @@ import ( historymodel "github.com/grafana/grafana/pkg/services/ngalert/state/historian/model" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tests/testsuite" - "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) @@ -59,21 +59,15 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) { "title": "Dashboard 2", }), }) - - knownUIDs := &sync.Map{} - generator := ngmodels.AlertRuleGen( - ngmodels.WithUniqueUID(knownUIDs), - ngmodels.WithUniqueID(), - ngmodels.WithOrgID(1), - ) + gen := ngmodels.RuleGen.With(ngmodels.RuleGen.WithOrgID(1)) dashboardRules := map[string][]*ngmodels.AlertRule{ dashboard1.UID: { - createAlertRuleFromDashboard(t, sql, "Test Rule 1", *dashboard1, generator), - createAlertRuleFromDashboard(t, sql, "Test Rule 2", *dashboard1, generator), + createAlertRuleFromDashboard(t, sql, "Test Rule 1", *dashboard1, gen), + createAlertRuleFromDashboard(t, sql, "Test Rule 2", *dashboard1, gen), }, dashboard2.UID: { - createAlertRuleFromDashboard(t, sql, "Test Rule 3", *dashboard2, generator), + createAlertRuleFromDashboard(t, sql, "Test Rule 3", *dashboard2, gen), }, } @@ -343,7 +337,7 @@ func TestIntegrationAlertStateHistoryStore(t *testing.T) { rule := dashboardRules[dashboard1.UID][0] stream1 := historian.StatesToStream(ruleMetaFromRule(t, rule), transitions, map[string]string{}, log.NewNopLogger()) - rule = createAlertRule(t, sql, "Test rule", generator) + rule = createAlertRule(t, sql, "Test rule", gen) stream2 := historian.StatesToStream(ruleMetaFromRule(t, rule), transitions, map[string]string{}, log.NewNopLogger()) stream := historian.Stream{ @@ -591,14 +585,15 @@ func createTestLokiStore(t *testing.T, sql db.DB, client lokiQueryClient) *LokiH // createAlertRule creates an alert rule in the database and returns it. // If a generator is not specified, uniqueness of primary key is not guaranteed. -func createAlertRule(t *testing.T, sql db.DB, title string, generator func() *ngmodels.AlertRule) *ngmodels.AlertRule { +func createAlertRule(t *testing.T, sql db.DB, title string, generator *ngmodels.AlertRuleGenerator) *ngmodels.AlertRule { t.Helper() if generator == nil { - generator = ngmodels.AlertRuleGen(ngmodels.WithTitle(title), withDashboardUID(nil), withPanelID(nil), ngmodels.WithOrgID(1)) + g := ngmodels.RuleGen + generator = g.With(g.WithTitle(title), g.WithDashboardAndPanel(nil, nil), g.WithOrgID(1)) } - rule := generator() + rule := generator.GenerateRef() // ensure rule has correct values if rule.Title != title { rule.Title = title @@ -632,17 +627,18 @@ func createAlertRule(t *testing.T, sql db.DB, title string, generator func() *ng // createAlertRuleFromDashboard creates an alert rule with a linked dashboard and panel in the database and returns it. // If a generator is not specified, uniqueness of primary key is not guaranteed. -func createAlertRuleFromDashboard(t *testing.T, sql db.DB, title string, dashboard dashboards.Dashboard, generator func() *ngmodels.AlertRule) *ngmodels.AlertRule { +func createAlertRuleFromDashboard(t *testing.T, sql db.DB, title string, dashboard dashboards.Dashboard, generator *ngmodels.AlertRuleGenerator) *ngmodels.AlertRule { t.Helper() panelID := new(int64) *panelID = 123 if generator == nil { - generator = ngmodels.AlertRuleGen(ngmodels.WithTitle(title), ngmodels.WithOrgID(1), withDashboardUID(&dashboard.UID), withPanelID(panelID)) + g := ngmodels.RuleGen + generator = g.With(g.WithTitle(title), g.WithDashboardAndPanel(&dashboard.UID, panelID), g.WithOrgID(1)) } - rule := generator() + rule := generator.GenerateRef() // ensure rule has correct values if rule.Title != title { rule.Title = title @@ -741,18 +737,6 @@ func genStateTransitions(t *testing.T, num int, start time.Time) []state.StateTr return transitions } -func withDashboardUID(dashboardUID *string) ngmodels.AlertRuleMutator { - return func(rule *ngmodels.AlertRule) { - rule.DashboardUID = dashboardUID - } -} - -func withPanelID(panelID *int64) ngmodels.AlertRuleMutator { - return func(rule *ngmodels.AlertRule) { - rule.PanelID = panelID - } -} - func compareAnnotationItem(t *testing.T, expected, actual *annotations.ItemDTO) { require.Equal(t, expected.AlertID, actual.AlertID) require.Equal(t, expected.AlertName, actual.AlertName) diff --git a/pkg/services/ngalert/accesscontrol/rules_test.go b/pkg/services/ngalert/accesscontrol/rules_test.go index 05301b1c2ae..d426d0ddd90 100644 --- a/pkg/services/ngalert/accesscontrol/rules_test.go +++ b/pkg/services/ngalert/accesscontrol/rules_test.go @@ -92,6 +92,8 @@ func createUserWithPermissions(permissions map[string][]string) identity.Request func TestAuthorizeRuleChanges(t *testing.T) { groupKey := models.GenerateGroupKey(rand.Int63()) namespaceIdScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(groupKey.NamespaceUID) + gen := models.RuleGen + genWithGroupKey := gen.With(gen.WithGroupKey(groupKey)) testCases := []struct { name string @@ -103,7 +105,7 @@ func TestAuthorizeRuleChanges(t *testing.T) { changes: func() *store.GroupDelta { return &store.GroupDelta{ GroupKey: groupKey, - New: models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey))), + New: genWithGroupKey.GenerateManyRef(1, 5), Update: nil, Delete: nil, } @@ -132,8 +134,8 @@ func TestAuthorizeRuleChanges(t *testing.T) { { name: "if there are rules to delete it should check delete action and query for datasource", changes: func() *store.GroupDelta { - rules := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey))) - rules2 := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey))) + rules := genWithGroupKey.GenerateManyRef(1, 5) + rules2 := genWithGroupKey.GenerateManyRef(1, 5) return &store.GroupDelta{ GroupKey: groupKey, AffectedGroups: map[models.AlertRuleGroupKey]models.RulesGroup{ @@ -162,8 +164,8 @@ func TestAuthorizeRuleChanges(t *testing.T) { { name: "if there are rules to update within the same namespace it should check update action and access to datasource", changes: func() *store.GroupDelta { - rules1 := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey))) - rules := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey))) + rules1 := genWithGroupKey.GenerateManyRef(1, 5) + rules := genWithGroupKey.GenerateManyRef(1, 5) updates := make([]store.RuleDelta, 0, len(rules)) for _, rule := range rules { @@ -207,18 +209,14 @@ func TestAuthorizeRuleChanges(t *testing.T) { { name: "if there are rules that are moved between namespaces it should check delete+add action and access to group where rules come from", changes: func() *store.GroupDelta { - rules1 := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey))) - rules := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey))) + rules1 := genWithGroupKey.GenerateManyRef(1, 5) + rules := genWithGroupKey.GenerateManyRef(1, 5) targetGroupKey := models.GenerateGroupKey(groupKey.OrgID) updates := make([]store.RuleDelta, 0, len(rules)) for _, rule := range rules { - cp := models.CopyRule(rule) - models.WithGroupKey(targetGroupKey)(cp) - cp.Data = []models.AlertQuery{ - models.GenerateAlertQuery(), - } + cp := models.CopyRule(rule, gen.WithGroupKey(targetGroupKey), gen.WithQuery(gen.GenerateQuery())) updates = append(updates, store.RuleDelta{ Existing: rule, @@ -269,8 +267,8 @@ func TestAuthorizeRuleChanges(t *testing.T) { NamespaceUID: groupKey.NamespaceUID, RuleGroup: util.GenerateShortUID(), } - sourceGroup := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(groupKey))) - targetGroup := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithGroupKey(targetGroupKey))) + sourceGroup := genWithGroupKey.GenerateManyRef(1, 5) + targetGroup := gen.With(gen.WithGroupKey(targetGroupKey)).GenerateManyRef(1, 5) updates := make([]store.RuleDelta, 0, len(sourceGroup)) toCopy := len(sourceGroup) @@ -279,11 +277,7 @@ func TestAuthorizeRuleChanges(t *testing.T) { } for i := 0; i < toCopy; i++ { rule := sourceGroup[0] - cp := models.CopyRule(rule) - models.WithGroupKey(targetGroupKey)(cp) - cp.Data = []models.AlertQuery{ - models.GenerateAlertQuery(), - } + cp := models.CopyRule(rule, gen.WithGroupKey(targetGroupKey), gen.WithQuery(models.GenerateAlertQuery())) updates = append(updates, store.RuleDelta{ Existing: rule, @@ -379,7 +373,7 @@ func TestAuthorizeRuleChanges(t *testing.T) { } func TestCheckDatasourcePermissionsForRule(t *testing.T) { - rule := models.AlertRuleGen()() + rule := models.RuleGen.GenerateRef() expressionByType := models.GenerateAlertQuery() expressionByType.QueryType = expr.DatasourceType @@ -442,7 +436,7 @@ func TestCheckDatasourcePermissionsForRule(t *testing.T) { func Test_authorizeAccessToRuleGroup(t *testing.T) { t.Run("should return true if user has access to all datasources of all rules in group", func(t *testing.T) { - rules := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen()) + rules := models.RuleGen.GenerateManyRef(1, 5) var scopes []string for _, rule := range rules { for _, query := range rule.Data { @@ -470,7 +464,9 @@ func Test_authorizeAccessToRuleGroup(t *testing.T) { }) t.Run("should return false if user does not have access to at least one rule in group", func(t *testing.T) { f := &folder.Folder{UID: "test-folder"} - rules := models.GenerateAlertRules(rand.Intn(4)+1, models.AlertRuleGen(models.WithNamespace(f))) + gen := models.RuleGen + genWithFolder := gen.With(gen.WithNamespace(f)) + rules := genWithFolder.GenerateManyRef(1, 5) var scopes []string for _, rule := range rules { for _, query := range rule.Data { @@ -487,7 +483,7 @@ func Test_authorizeAccessToRuleGroup(t *testing.T) { datasources.ActionQuery: scopes, } - rule := models.AlertRuleGen(models.WithNamespace(f))() + rule := genWithFolder.GenerateRef() rules = append(rules, rule) ac := &recordingAccessControlFake{} diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go index 86a3c328f82..905bc81e99b 100644 --- a/pkg/services/ngalert/api/api_prometheus_test.go +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "math/rand" "net/http" "testing" "time" @@ -21,7 +20,6 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/datasources" - "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/eval" @@ -287,6 +285,8 @@ func TestRouteGetRuleStatuses(t *testing.T) { timeNow = func() time.Time { return time.Date(2022, 3, 10, 14, 0, 0, 0, time.UTC) } orgID := int64(1) + gen := ngmodels.RuleGen + gen = gen.With(gen.WithOrgID(orgID)) queryPermissions := map[int64]map[string][]string{1: {datasources.ActionQuery: {datasources.ScopeAll}}} req, err := http.NewRequest("GET", "/api/v1/rules", nil) @@ -496,7 +496,8 @@ func TestRouteGetRuleStatuses(t *testing.T) { ruleStore := fakes.NewRuleStore(t) fakeAIM := NewFakeAlertInstanceManager(t) groupKey := ngmodels.GenerateGroupKey(orgID) - _, rules := ngmodels.GenerateUniqueAlertRules(rand.Intn(5)+5, ngmodels.AlertRuleGen(withGroupKey(groupKey), ngmodels.WithUniqueGroupIndex())) + gen := ngmodels.RuleGen + rules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex()).GenerateManyRef(5, 10) ruleStore.PutRule(context.Background(), rules...) api := PrometheusSrv{ @@ -539,9 +540,9 @@ func TestRouteGetRuleStatuses(t *testing.T) { ruleStore := fakes.NewRuleStore(t) fakeAIM := NewFakeAlertInstanceManager(t) - rules := ngmodels.GenerateAlertRules(rand.Intn(4)+2, ngmodels.AlertRuleGen(withOrgID(orgID))) + rules := gen.GenerateManyRef(2, 6) ruleStore.PutRule(context.Background(), rules...) - ruleStore.PutRule(context.Background(), ngmodels.GenerateAlertRules(rand.Intn(4)+2, ngmodels.AlertRuleGen(withOrgID(orgID)))...) + ruleStore.PutRule(context.Background(), gen.GenerateManyRef(2, 6)...) api := PrometheusSrv{ log: log.NewNopLogger(), @@ -575,9 +576,11 @@ func TestRouteGetRuleStatuses(t *testing.T) { t.Run("test totals are expected", func(t *testing.T) { fakeStore, fakeAIM, api := setupAPI(t) // Create rules in the same Rule Group to keep assertions simple - rules := ngmodels.GenerateAlertRules(3, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{ - Title: "Folder-1", - }))) + rules := gen.With(gen.WithGroupKey(ngmodels.AlertRuleGroupKey{ + RuleGroup: "Rule-Group-1", + NamespaceUID: "Folder-1", + OrgID: orgID, + })).GenerateManyRef(3) // Need to sort these so we add alerts to the rules as ordered in the response ngmodels.AlertRulesBy(ngmodels.AlertRulesByIndex).Sort(rules) // The last two rules will have errors, however the first will be alerting @@ -635,7 +638,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { t.Run("test time of first firing alert", func(t *testing.T) { fakeStore, fakeAIM, api := setupAPI(t) // Create rules in the same Rule Group to keep assertions simple - rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID))) + rules := gen.GenerateManyRef(1) fakeStore.PutRule(context.Background(), rules...) getRuleResponse := func() apimodels.RuleResponse { @@ -691,7 +694,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { t.Run("test with limit on Rule Groups", func(t *testing.T) { fakeStore, _, api := setupAPI(t) - rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID))) + rules := gen.GenerateManyRef(2) fakeStore.PutRule(context.Background(), rules...) t.Run("first without limit", func(t *testing.T) { @@ -763,7 +766,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { t.Run("test with limit rules", func(t *testing.T) { fakeStore, _, api := setupAPI(t) - rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"))) + rules := gen.With(gen.WithGroupName("Rule-Group-1")).GenerateManyRef(2) fakeStore.PutRule(context.Background(), rules...) t.Run("first without limit", func(t *testing.T) { @@ -836,7 +839,7 @@ func TestRouteGetRuleStatuses(t *testing.T) { t.Run("test with limit alerts", func(t *testing.T) { fakeStore, fakeAIM, api := setupAPI(t) - rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"))) + rules := gen.With(gen.WithGroupName("Rule-Group-1")).GenerateManyRef(2) fakeStore.PutRule(context.Background(), rules...) // create a normal and firing alert for each rule for _, r := range rules { @@ -927,9 +930,11 @@ func TestRouteGetRuleStatuses(t *testing.T) { fakeStore, fakeAIM, api := setupAPI(t) // create two rules in the same Rule Group to keep assertions simple - rules := ngmodels.GenerateAlertRules(3, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{ - Title: "Folder-1", - }))) + rules := gen.With(gen.WithGroupKey(ngmodels.AlertRuleGroupKey{ + NamespaceUID: "Folder-1", + RuleGroup: "Rule-Group-1", + OrgID: orgID, + })).GenerateManyRef(2) // Need to sort these so we add alerts to the rules as ordered in the response ngmodels.AlertRulesBy(ngmodels.AlertRulesByIndex).Sort(rules) // The last two rules will have errors, however the first will be alerting @@ -1084,9 +1089,11 @@ func TestRouteGetRuleStatuses(t *testing.T) { t.Run("test with matcher on labels", func(t *testing.T) { fakeStore, fakeAIM, api := setupAPI(t) // create two rules in the same Rule Group to keep assertions simple - rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{ - Title: "Folder-1", - }))) + rules := gen.With(gen.WithGroupKey(ngmodels.AlertRuleGroupKey{ + NamespaceUID: "Folder-1", + RuleGroup: "Rule-Group-1", + OrgID: orgID, + })).GenerateManyRef(1) fakeStore.PutRule(context.Background(), rules...) // create a normal and alerting state for each rule @@ -1268,12 +1275,13 @@ func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, Promet return fakeStore, fakeAIM, api } -func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *fakes.RuleStore, query func(r *ngmodels.AlertRule)) { +func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *fakes.RuleStore, query ngmodels.AlertRuleMutator) { t.Helper() - rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID), asFixture(), query)) + gen := ngmodels.RuleGen + r := gen.With(gen.WithOrgID(orgID), asFixture(), query).GenerateRef() - fakeAIM.GenerateAlertInstances(orgID, rules[0].UID, 1, func(s *state.State) *state.State { + fakeAIM.GenerateAlertInstances(orgID, r.UID, 1, func(s *state.State) *state.State { s.Labels = data.Labels{ "job": "prometheus", alertingModels.NamespaceUIDLabel: "test_namespace_uid", @@ -1283,14 +1291,12 @@ func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAl return s }) - for _, r := range rules { - fakeStore.PutRule(context.Background(), r) - } + fakeStore.PutRule(context.Background(), r) } // asFixture removes variable values of the alert rule. // we're not too interested in variability of the rule in this scenario. -func asFixture() func(r *ngmodels.AlertRule) { +func asFixture() ngmodels.AlertRuleMutator { return func(r *ngmodels.AlertRule) { r.Title = "AlwaysFiring" r.NamespaceUID = "namespaceUID" @@ -1306,7 +1312,7 @@ func asFixture() func(r *ngmodels.AlertRule) { } } -func withClassicConditionSingleQuery() func(r *ngmodels.AlertRule) { +func withClassicConditionSingleQuery() ngmodels.AlertRuleMutator { return func(r *ngmodels.AlertRule) { queries := []ngmodels.AlertQuery{ { @@ -1328,7 +1334,7 @@ func withClassicConditionSingleQuery() func(r *ngmodels.AlertRule) { } } -func withExpressionsMultiQuery() func(r *ngmodels.AlertRule) { +func withExpressionsMultiQuery() ngmodels.AlertRuleMutator { return func(r *ngmodels.AlertRule) { queries := []ngmodels.AlertQuery{ { diff --git a/pkg/services/ngalert/api/api_ruler_export_test.go b/pkg/services/ngalert/api/api_ruler_export_test.go index ae91fb0389a..b1281045a00 100644 --- a/pkg/services/ngalert/api/api_ruler_export_test.go +++ b/pkg/services/ngalert/api/api_ruler_export_test.go @@ -9,7 +9,6 @@ import ( "path" "sort" "strings" - "sync" "testing" "github.com/stretchr/testify/assert" @@ -198,7 +197,6 @@ func TestExportFromPayload(t *testing.T) { } func TestExportRules(t *testing.T) { - uids := sync.Map{} orgID := int64(1) f1 := randFolder() f2 := randFolder() @@ -210,33 +208,20 @@ func TestExportRules(t *testing.T) { NamespaceUID: f1.UID, RuleGroup: "HAS-ACCESS-1", } - accessQuery := ngmodels.GenerateAlertQuery() - noAccessQuery := ngmodels.GenerateAlertQuery() - _, hasAccess1 := ngmodels.GenerateUniqueAlertRules(5, - ngmodels.AlertRuleGen( - ngmodels.WithUniqueUID(&uids), - withGroupKey(hasAccessKey1), - ngmodels.WithQuery(accessQuery), - ngmodels.WithUniqueGroupIndex(), - )) + gen := ngmodels.RuleGen + accessQuery := gen.GenerateQuery() + noAccessQuery := gen.GenerateQuery() + + hasAccess1 := gen.With(gen.WithGroupKey(hasAccessKey1), gen.WithQuery(accessQuery), gen.WithUniqueGroupIndex()).GenerateManyRef(5) ruleStore.PutRule(context.Background(), hasAccess1...) noAccessKey1 := ngmodels.AlertRuleGroupKey{ OrgID: orgID, NamespaceUID: f1.UID, RuleGroup: "NO-ACCESS", } - _, noAccess1 := ngmodels.GenerateUniqueAlertRules(5, - ngmodels.AlertRuleGen( - ngmodels.WithUniqueUID(&uids), - withGroupKey(noAccessKey1), - ngmodels.WithQuery(noAccessQuery), - )) - noAccessRule := ngmodels.AlertRuleGen( - ngmodels.WithUniqueUID(&uids), - withGroupKey(noAccessKey1), - ngmodels.WithQuery(accessQuery), - )() + noAccess1 := gen.With(gen.WithGroupKey(noAccessKey1), gen.WithQuery(noAccessQuery)).GenerateManyRef(5) + noAccessRule := gen.With(gen.WithGroupKey(noAccessKey1), gen.WithQuery(accessQuery)).GenerateRef() noAccess1 = append(noAccess1, noAccessRule) ruleStore.PutRule(context.Background(), noAccess1...) @@ -245,21 +230,10 @@ func TestExportRules(t *testing.T) { NamespaceUID: f2.UID, RuleGroup: "HAS-ACCESS-2", } - _, hasAccess2 := ngmodels.GenerateUniqueAlertRules(5, - ngmodels.AlertRuleGen( - ngmodels.WithUniqueUID(&uids), - withGroupKey(hasAccessKey2), - ngmodels.WithQuery(accessQuery), - ngmodels.WithUniqueGroupIndex(), - )) + hasAccess2 := gen.With(gen.WithGroupKey(hasAccessKey2), gen.WithQuery(accessQuery), gen.WithUniqueGroupIndex()).GenerateManyRef(5) ruleStore.PutRule(context.Background(), hasAccess2...) - _, noAccessByFolder := ngmodels.GenerateUniqueAlertRules(10, - ngmodels.AlertRuleGen( - ngmodels.WithUniqueUID(&uids), - ngmodels.WithQuery(accessQuery), // no access because of folder - ngmodels.WithNamespaceUIDNotIn(f1.UID, f2.UID), - )) + noAccessByFolder := gen.With(gen.WithQuery(accessQuery), gen.WithNamespaceUIDNotIn(f1.UID, f2.UID)).GenerateManyRef(10) ruleStore.PutRule(context.Background(), noAccessByFolder...) // overwrite the folders visible to user because PutRule automatically creates folders in the fake store. diff --git a/pkg/services/ngalert/api/api_ruler_test.go b/pkg/services/ngalert/api/api_ruler_test.go index 7856e858088..99c109c00ca 100644 --- a/pkg/services/ngalert/api/api_ruler_test.go +++ b/pkg/services/ngalert/api/api_ruler_test.go @@ -33,7 +33,6 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util/cmputil" "github.com/grafana/grafana/pkg/web" ) @@ -67,12 +66,13 @@ func TestRouteDeleteAlertRules(t *testing.T) { orgID := rand.Int63() folder := randFolder() + gen := models.RuleGen.With(models.RuleGen.WithOrgID(orgID)) initFakeRuleStore := func(t *testing.T) *fakes.RuleStore { ruleStore := fakes.NewRuleStore(t) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) // add random data - ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID)))...) + ruleStore.PutRule(context.Background(), gen.GenerateManyRef(1, 5)...) return ruleStore } @@ -80,7 +80,7 @@ func TestRouteDeleteAlertRules(t *testing.T) { t.Run("and group argument is empty", func(t *testing.T) { t.Run("return Forbidden if user is not authorized to access any group in the folder", func(t *testing.T) { ruleStore := initFakeRuleStore(t) - ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...) + ruleStore.PutRule(context.Background(), gen.With(gen.WithNamespace(folder)).GenerateManyRef(1, 5)...) request := createRequestContextWithPerms(orgID, map[int64]map[string][]string{}, nil) @@ -93,16 +93,20 @@ func TestRouteDeleteAlertRules(t *testing.T) { ruleStore := initFakeRuleStore(t) provisioningStore := fakes.NewFakeProvisioningStore() - authorizedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("authz_"+util.GenerateShortUID()))) + folderGen := gen.With(gen.WithNamespace(folder)) - provisionedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("provisioned_"+util.GenerateShortUID()))) - err := provisioningStore.SetProvenance(context.Background(), provisionedRulesInFolder[0], orgID, models.ProvenanceAPI) - require.NoError(t, err) + authorizedRulesInFolder := folderGen.With(gen.WithGroupPrefix("authz-")).GenerateManyRef(1, 5) + + provisionedRulesInFolder := folderGen.With(gen.WithGroupPrefix("provisioned-")).GenerateManyRef(1, 5) + for _, rule := range provisionedRulesInFolder { + err := provisioningStore.SetProvenance(context.Background(), rule, orgID, models.ProvenanceAPI) + require.NoError(t, err) + } ruleStore.PutRule(context.Background(), authorizedRulesInFolder...) ruleStore.PutRule(context.Background(), provisionedRulesInFolder...) // more rules in the same namespace but user does not have access to them - ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("unauthz"+util.GenerateShortUID())))...) + ruleStore.PutRule(context.Background(), folderGen.With(gen.WithGroupPrefix("unauthz")).GenerateManyRef(1, 5)...) permissions := createPermissionsForRules(append(authorizedRulesInFolder, provisionedRulesInFolder...), orgID) requestCtx := createRequestContextWithPerms(orgID, permissions, nil) @@ -116,13 +120,15 @@ func TestRouteDeleteAlertRules(t *testing.T) { ruleStore := initFakeRuleStore(t) provisioningStore := fakes.NewFakeProvisioningStore() - provisionedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(util.GenerateShortUID()))) + folderGen := gen.With(gen.WithNamespace(folder)) + + provisionedRulesInFolder := folderGen.With(gen.WithSameGroup()).GenerateManyRef(1, 5) err := provisioningStore.SetProvenance(context.Background(), provisionedRulesInFolder[0], orgID, models.ProvenanceAPI) require.NoError(t, err) ruleStore.PutRule(context.Background(), provisionedRulesInFolder...) // more rules in the same namespace but user does not have access to them - ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(util.GenerateShortUID())))...) + ruleStore.PutRule(context.Background(), folderGen.With(gen.WithSameGroup()).GenerateManyRef(1, 5)...) permissions := createPermissionsForRules(provisionedRulesInFolder, orgID) requestCtx := createRequestContextWithPerms(orgID, permissions, nil) @@ -143,19 +149,20 @@ func TestRouteDeleteAlertRules(t *testing.T) { }) }) t.Run("and group argument is not empty", func(t *testing.T) { - groupName := util.GenerateShortUID() t.Run("return Forbidden if user is not authorized to access the group", func(t *testing.T) { ruleStore := initFakeRuleStore(t) - authorizedRulesInGroup := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName))) + groupGen := gen.With(gen.WithNamespace(folder), gen.WithSameGroup()) + + authorizedRulesInGroup := groupGen.GenerateManyRef(1, 5) ruleStore.PutRule(context.Background(), authorizedRulesInGroup...) // more rules in the same group but user is not authorized to access them - ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))...) + ruleStore.PutRule(context.Background(), groupGen.GenerateManyRef(1, 5)...) permissions := createPermissionsForRules(authorizedRulesInGroup, orgID) requestCtx := createRequestContextWithPerms(orgID, permissions, nil) - response := createService(ruleStore).RouteDeleteAlertRules(requestCtx, folder.UID, groupName) + response := createService(ruleStore).RouteDeleteAlertRules(requestCtx, folder.UID, authorizedRulesInGroup[0].RuleGroup) require.Equalf(t, http.StatusForbidden, response.Status(), "Expected 403 but got %d: %v", response.Status(), string(response.Body())) deleteCommands := getRecordedCommand(ruleStore) @@ -165,7 +172,9 @@ func TestRouteDeleteAlertRules(t *testing.T) { ruleStore := initFakeRuleStore(t) provisioningStore := fakes.NewFakeProvisioningStore() - provisionedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName))) + groupGen := gen.With(gen.WithNamespace(folder), gen.WithSameGroup()) + + provisionedRulesInFolder := groupGen.GenerateManyRef(1, 5) err := provisioningStore.SetProvenance(context.Background(), provisionedRulesInFolder[0], orgID, models.ProvenanceAPI) require.NoError(t, err) @@ -174,7 +183,7 @@ func TestRouteDeleteAlertRules(t *testing.T) { permissions := createPermissionsForRules(provisionedRulesInFolder, orgID) requestCtx := createRequestContextWithPerms(orgID, permissions, nil) - response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.UID, groupName) + response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.UID, provisionedRulesInFolder[0].RuleGroup) require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body())) deleteCommands := getRecordedCommand(ruleStore) @@ -185,15 +194,17 @@ func TestRouteDeleteAlertRules(t *testing.T) { } func TestRouteGetNamespaceRulesConfig(t *testing.T) { + gen := models.RuleGen t.Run("fine-grained access is enabled", func(t *testing.T) { t.Run("should return rules for which user has access to data source", func(t *testing.T) { orgID := rand.Int63() folder := randFolder() ruleStore := fakes.NewRuleStore(t) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) - expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder))) + folderGen := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder)) + expectedRules := folderGen.GenerateManyRef(2, 6) ruleStore.PutRule(context.Background(), expectedRules...) - ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...) + ruleStore.PutRule(context.Background(), folderGen.GenerateManyRef(2, 6)...) permissions := createPermissionsForRules(expectedRules, orgID) req := createRequestContextWithPerms(orgID, permissions, nil) @@ -227,7 +238,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) { folder := randFolder() ruleStore := fakes.NewRuleStore(t) ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder) - expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder))) + expectedRules := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder)).GenerateManyRef(2, 6) ruleStore.PutRule(context.Background(), expectedRules...) svc := createService(ruleStore) @@ -271,7 +282,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) { groupKey := models.GenerateGroupKey(orgID) groupKey.NamespaceUID = folder.UID - expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex())) + expectedRules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex()).GenerateManyRef(5, 10) ruleStore.PutRule(context.Background(), expectedRules...) perms := createPermissionsForRules(expectedRules, orgID) @@ -308,6 +319,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) { } func TestRouteGetRulesConfig(t *testing.T) { + gen := models.RuleGen t.Run("fine-grained access is enabled", func(t *testing.T) { t.Run("should check access to data source", func(t *testing.T) { orgID := rand.Int63() @@ -321,8 +333,8 @@ func TestRouteGetRulesConfig(t *testing.T) { group2Key := models.GenerateGroupKey(orgID) group2Key.NamespaceUID = folder2.UID - group1 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group1Key))) - group2 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group2Key))) + group1 := gen.With(gen.WithGroupKey(group1Key)).GenerateManyRef(2, 6) + group2 := gen.With(gen.WithGroupKey(group2Key)).GenerateManyRef(2, 6) ruleStore.PutRule(context.Background(), append(group1, group2...)...) t.Run("and do not return group if user does not have access to one of rules", func(t *testing.T) { @@ -355,7 +367,7 @@ func TestRouteGetRulesConfig(t *testing.T) { groupKey := models.GenerateGroupKey(orgID) groupKey.NamespaceUID = folder.UID - expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex())) + expectedRules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex()).GenerateManyRef(5, 10) ruleStore.PutRule(context.Background(), expectedRules...) perms := createPermissionsForRules(expectedRules, orgID) @@ -392,6 +404,7 @@ func TestRouteGetRulesConfig(t *testing.T) { } func TestRouteGetRulesGroupConfig(t *testing.T) { + gen := models.RuleGen t.Run("fine-grained access is enabled", func(t *testing.T) { t.Run("should check access to data source", func(t *testing.T) { orgID := rand.Int63() @@ -401,7 +414,7 @@ func TestRouteGetRulesGroupConfig(t *testing.T) { groupKey := models.GenerateGroupKey(orgID) groupKey.NamespaceUID = folder.UID - expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(groupKey))) + expectedRules := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(2, 6) ruleStore.PutRule(context.Background(), expectedRules...) t.Run("and return Forbidden if user does not have access one of rules", func(t *testing.T) { @@ -439,7 +452,7 @@ func TestRouteGetRulesGroupConfig(t *testing.T) { groupKey := models.GenerateGroupKey(orgID) groupKey.NamespaceUID = folder.UID - expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex())) + expectedRules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex()).GenerateManyRef(5, 10) ruleStore.PutRule(context.Background(), expectedRules...) perms := createPermissionsForRules(expectedRules, orgID) @@ -475,14 +488,15 @@ func TestVerifyProvisionedRulesNotAffected(t *testing.T) { orgID := rand.Int63() group := models.GenerateGroupKey(orgID) affectedGroups := make(map[models.AlertRuleGroupKey]models.RulesGroup) + gen := models.RuleGen var allRules []*models.AlertRule { - rules := models.GenerateAlertRules(rand.Intn(3)+1, models.AlertRuleGen(withGroupKey(group))) + rules := gen.With(gen.WithGroupKey(group)).GenerateManyRef(1, 4) allRules = append(allRules, rules...) affectedGroups[group] = rules for i := 0; i < rand.Intn(3)+1; i++ { g := models.GenerateGroupKey(orgID) - rules := models.GenerateAlertRules(rand.Intn(3)+1, models.AlertRuleGen(withGroupKey(g))) + rules := gen.With(gen.WithGroupKey(g)).GenerateManyRef(1, 4) allRules = append(allRules, rules...) affectedGroups[g] = rules } @@ -533,20 +547,15 @@ func TestVerifyProvisionedRulesNotAffected(t *testing.T) { } func TestValidateQueries(t *testing.T) { + gen := models.RuleGen delta := store.GroupDelta{ New: []*models.AlertRule{ - models.AlertRuleGen(func(rule *models.AlertRule) { - rule.Condition = "New" - })(), + gen.With(gen.WithCondition("New")).GenerateRef(), }, Update: []store.RuleDelta{ { - Existing: models.AlertRuleGen(func(rule *models.AlertRule) { - rule.Condition = "Update_Existing" - })(), - New: models.AlertRuleGen(func(rule *models.AlertRule) { - rule.Condition = "Update_New" - })(), + Existing: gen.With(gen.WithCondition("New")).GenerateRef(), + New: gen.With(gen.WithCondition("Update_New")).GenerateRef(), Diff: cmputil.DiffReport{ cmputil.Diff{ Path: "SomeField", @@ -554,12 +563,8 @@ func TestValidateQueries(t *testing.T) { }, }, { - Existing: models.AlertRuleGen(func(rule *models.AlertRule) { - rule.Condition = "Update_Index_Existing" - })(), - New: models.AlertRuleGen(func(rule *models.AlertRule) { - rule.Condition = "Update_Index_New" - })(), + Existing: gen.With(gen.WithCondition("Update_Index_Existing")).GenerateRef(), + New: gen.With(gen.WithCondition("Update_Index_New")).GenerateRef(), Diff: cmputil.DiffReport{ cmputil.Diff{ Path: "RuleGroupIndex", @@ -567,11 +572,7 @@ func TestValidateQueries(t *testing.T) { }, }, }, - Delete: []*models.AlertRule{ - models.AlertRuleGen(func(rule *models.AlertRule) { - rule.Condition = "Deleted" - })(), - }, + Delete: gen.With(gen.WithCondition("Deleted")).GenerateManyRef(1), } t.Run("should not validate deleted rules or updated rules with ignored fields", func(t *testing.T) { @@ -694,29 +695,3 @@ func createPermissionsForRules(rules []*models.AlertRule, orgID int64) map[int64 } return map[int64]map[string][]string{orgID: permissions} } - -func withOrgID(orgId int64) func(rule *models.AlertRule) { - return func(rule *models.AlertRule) { - rule.OrgID = orgId - } -} - -func withGroup(groupName string) func(rule *models.AlertRule) { - return func(rule *models.AlertRule) { - rule.RuleGroup = groupName - } -} - -func withNamespace(namespace *folder.Folder) func(rule *models.AlertRule) { - return func(rule *models.AlertRule) { - rule.NamespaceUID = namespace.UID - } -} - -func withGroupKey(groupKey models.AlertRuleGroupKey) func(rule *models.AlertRule) { - return func(rule *models.AlertRule) { - rule.RuleGroup = groupKey.RuleGroup - rule.OrgID = groupKey.OrgID - rule.NamespaceUID = groupKey.NamespaceUID - } -} diff --git a/pkg/services/ngalert/api/api_testing_test.go b/pkg/services/ngalert/api/api_testing_test.go index becad5a73cc..a8bee7838dd 100644 --- a/pkg/services/ngalert/api/api_testing_test.go +++ b/pkg/services/ngalert/api/api_testing_test.go @@ -174,8 +174,9 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) { }) t.Run("should return Forbidden if user cannot query a data source", func(t *testing.T) { - data1 := models.GenerateAlertQuery() - data2 := models.GenerateAlertQuery() + gen := models.RuleGen + data1 := gen.GenerateQuery() + data2 := gen.GenerateQuery() ac := acMock.New().WithPermissions([]ac.Permission{ {Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)}, @@ -195,12 +196,14 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) { NamespaceTitle: f.Title, }) + t.Log(string(response.Body())) require.Equal(t, http.StatusForbidden, response.Status()) }) t.Run("should return 200 if user can query all data sources", func(t *testing.T) { - data1 := models.GenerateAlertQuery() - data2 := models.GenerateAlertQuery() + gen := models.RuleGen + data1 := gen.GenerateQuery() + data2 := gen.GenerateQuery() ac := acMock.New().WithPermissions([]ac.Permission{ {Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)}, @@ -252,8 +255,9 @@ func TestRouteEvalQueries(t *testing.T) { } t.Run("should return Forbidden if user cannot query a data source", func(t *testing.T) { - data1 := models.GenerateAlertQuery() - data2 := models.GenerateAlertQuery() + g := models.RuleGen + data1 := g.GenerateQuery() + data2 := g.GenerateQuery() srv := &TestingApiSrv{ authz: accesscontrol.NewRuleService(acMock.New().WithPermissions([]ac.Permission{ diff --git a/pkg/services/ngalert/api/util_test.go b/pkg/services/ngalert/api/util_test.go index 55f5731aa5c..e700898084d 100644 --- a/pkg/services/ngalert/api/util_test.go +++ b/pkg/services/ngalert/api/util_test.go @@ -143,15 +143,16 @@ func TestAlertingProxy_createProxyContext(t *testing.T) { } func Test_containsProvisionedAlerts(t *testing.T) { + gen := models2.RuleGen t.Run("should return true if at least one rule is provisioned", func(t *testing.T) { - _, rules := models2.GenerateUniqueAlertRules(rand.Intn(4)+2, models2.AlertRuleGen()) + rules := gen.GenerateManyRef(2, 6) provenance := map[string]models2.Provenance{ rules[rand.Intn(len(rules))].UID: []models2.Provenance{models2.ProvenanceAPI, models2.ProvenanceFile}[rand.Intn(2)], } require.Truef(t, containsProvisionedAlerts(provenance, rules), "the group of rules is expected to be considered as provisioned but it isn't. Provenances: %v", provenance) }) t.Run("should return false if map does not contain or has ProvenanceNone", func(t *testing.T) { - _, rules := models2.GenerateUniqueAlertRules(rand.Intn(5)+1, models2.AlertRuleGen()) + rules := gen.GenerateManyRef(1, 6) provenance := make(map[string]models2.Provenance) numProvenanceNone := rand.Intn(len(rules)) for i := 0; i < numProvenanceNone; i++ { diff --git a/pkg/services/ngalert/backtesting/engine_test.go b/pkg/services/ngalert/backtesting/engine_test.go index f8d058f1410..26bb317aaad 100644 --- a/pkg/services/ngalert/backtesting/engine_test.go +++ b/pkg/services/ngalert/backtesting/engine_test.go @@ -189,7 +189,8 @@ func TestEvaluatorTest(t *testing.T) { return manager }, } - rule := models.AlertRuleGen(models.WithInterval(time.Second))() + gen := models.RuleGen + rule := gen.With(gen.WithInterval(time.Second)).GenerateRef() ruleInterval := time.Duration(rule.IntervalSeconds) * time.Second t.Run("should return data frame in specific format", func(t *testing.T) { diff --git a/pkg/services/ngalert/models/alert_rule_test.go b/pkg/services/ngalert/models/alert_rule_test.go index f875a73d9d7..414507582f5 100644 --- a/pkg/services/ngalert/models/alert_rule_test.go +++ b/pkg/services/ngalert/models/alert_rule_test.go @@ -197,11 +197,10 @@ func TestSetDashboardAndPanelFromAnnotations(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - rule := AlertRuleGen(func(rule *AlertRule) { - rule.Annotations = tc.annotations - rule.DashboardUID = nil - rule.PanelID = nil - })() + rule := RuleGen.With( + RuleMuts.WithDashboardAndPanel(nil, nil), + RuleMuts.WithAnnotations(tc.annotations), + ).Generate() err := rule.SetDashboardAndPanelFromAnnotations() require.Equal(t, tc.expectedError, err) @@ -256,14 +255,16 @@ func TestPatchPartialAlertRule(t *testing.T) { }, } + gen := RuleGen.With( + RuleMuts.WithFor(time.Duration(rand.Int63n(1000) + 1)), + ) + for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { var existing *AlertRuleWithOptionals - for { - rule := AlertRuleGen(func(rule *AlertRule) { - rule.For = time.Duration(rand.Int63n(1000) + 1) - })() - existing = &AlertRuleWithOptionals{AlertRule: *rule} + for i := 0; i < 10; i++ { + rule := gen.Generate() + existing = &AlertRuleWithOptionals{AlertRule: rule} cloned := *existing testCase.mutator(&cloned) if !cmp.Equal(existing, cloned, cmp.FilterPath(func(path cmp.Path) bool { @@ -343,15 +344,20 @@ func TestPatchPartialAlertRule(t *testing.T) { }, } + gen := RuleGen.With( + RuleMuts.WithUniqueID(), + RuleMuts.WithFor(time.Duration(rand.Int63n(1000)+1)), + ) + for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { var existing *AlertRule for { - existing = AlertRuleGen(WithUniqueID())() - cloned := *existing + existing = gen.GenerateRef() + cloned := CopyRule(existing) // make sure the generated rule does not match the mutated one - testCase.mutator(&cloned) - if !cmp.Equal(*existing, cloned, cmp.FilterPath(func(path cmp.Path) bool { + testCase.mutator(cloned) + if !cmp.Equal(existing, cloned, cmp.FilterPath(func(path cmp.Path) bool { return path.String() == "Data.modelProps" }, cmp.Ignore())) { break @@ -368,14 +374,14 @@ func TestPatchPartialAlertRule(t *testing.T) { func TestDiff(t *testing.T) { t.Run("should return nil if there is no diff", func(t *testing.T) { - rule1 := AlertRuleGen()() + rule1 := RuleGen.GenerateRef() rule2 := CopyRule(rule1) result := rule1.Diff(rule2) require.Emptyf(t, result, "expected diff to be empty. rule1: %#v, rule2: %#v\ndiff: %s", rule1, rule2, result) }) t.Run("should respect fields to ignore", func(t *testing.T) { - rule1 := AlertRuleGen()() + rule1 := RuleGen.GenerateRef() rule2 := CopyRule(rule1) rule2.ID = rule1.ID/2 + 1 rule2.Version = rule1.Version/2 + 1 @@ -385,8 +391,8 @@ func TestDiff(t *testing.T) { }) t.Run("should find diff in simple fields", func(t *testing.T) { - rule1 := AlertRuleGen()() - rule2 := AlertRuleGen()() + rule1 := RuleGen.GenerateRef() + rule2 := RuleGen.GenerateRef() diffs := rule1.Diff(rule2, "Data", "Annotations", "Labels", "NotificationSettings") // these fields will be tested separately @@ -508,7 +514,7 @@ func TestDiff(t *testing.T) { }) t.Run("should not see difference between nil and empty Annotations", func(t *testing.T) { - rule1 := AlertRuleGen()() + rule1 := RuleGen.GenerateRef() rule1.Annotations = make(map[string]string) rule2 := CopyRule(rule1) rule2.Annotations = nil @@ -518,7 +524,7 @@ func TestDiff(t *testing.T) { }) t.Run("should detect changes in Annotations", func(t *testing.T) { - rule1 := AlertRuleGen()() + rule1 := RuleGen.GenerateRef() rule2 := CopyRule(rule1) rule1.Annotations = map[string]string{ @@ -555,7 +561,7 @@ func TestDiff(t *testing.T) { }) t.Run("should not see difference between nil and empty Labels", func(t *testing.T) { - rule1 := AlertRuleGen()() + rule1 := RuleGen.GenerateRef() rule1.Annotations = make(map[string]string) rule2 := CopyRule(rule1) rule2.Annotations = nil @@ -565,7 +571,7 @@ func TestDiff(t *testing.T) { }) t.Run("should detect changes in Labels", func(t *testing.T) { - rule1 := AlertRuleGen()() + rule1 := RuleGen.GenerateRef() rule2 := CopyRule(rule1) rule1.Labels = map[string]string{ @@ -602,7 +608,7 @@ func TestDiff(t *testing.T) { }) t.Run("should detect changes in Data", func(t *testing.T) { - rule1 := AlertRuleGen()() + rule1 := RuleGen.GenerateRef() rule2 := CopyRule(rule1) query1 := AlertQuery{ @@ -658,11 +664,11 @@ func TestDiff(t *testing.T) { t.Run("should correctly detect no change with '<' and '>' in query", func(t *testing.T) { old := query1 - new := query1 + newQuery := query1 old.Model = json.RawMessage(`{"field1": "$A \u003c 1"}`) - new.Model = json.RawMessage(`{"field1": "$A < 1"}`) + newQuery.Model = json.RawMessage(`{"field1": "$A < 1"}`) rule1.Data = []AlertQuery{old} - rule2.Data = []AlertQuery{new} + rule2.Data = []AlertQuery{newQuery} diff := rule1.Diff(rule2) assert.Nil(t, diff) @@ -699,7 +705,7 @@ func TestDiff(t *testing.T) { }) t.Run("should detect changes in NotificationSettings", func(t *testing.T) { - rule1 := AlertRuleGen()() + rule1 := RuleGen.GenerateRef() baseSettings := NotificationSettingsGen(NSMuts.WithGroupBy("test1", "test2"))() rule1.NotificationSettings = []NotificationSettings{baseSettings} @@ -824,7 +830,9 @@ func TestSortByGroupIndex(t *testing.T) { } t.Run("should sort rules by GroupIndex", func(t *testing.T) { - rules := GenerateAlertRules(rand.Intn(15)+5, AlertRuleGen(WithUniqueGroupIndex())) + rules := RuleGen.With( + RuleMuts.WithUniqueGroupIndex(), + ).GenerateManyRef(5, 20) ensureNotSorted(t, rules, func(i, j int) bool { return rules[i].RuleGroupIndex < rules[j].RuleGroupIndex }) @@ -835,7 +843,10 @@ func TestSortByGroupIndex(t *testing.T) { }) t.Run("should sort by ID if same GroupIndex", func(t *testing.T) { - rules := GenerateAlertRules(rand.Intn(15)+5, AlertRuleGen(WithUniqueID(), WithGroupIndex(rand.Int()))) + rules := RuleGen.With( + RuleMuts.WithUniqueID(), + RuleMuts.WithGroupIndex(rand.Int()), + ).GenerateManyRef(5, 20) ensureNotSorted(t, rules, func(i, j int) bool { return rules[i].ID < rules[j].ID }) diff --git a/pkg/services/ngalert/models/testing.go b/pkg/services/ngalert/models/testing.go index f22f357f251..27deff6c000 100644 --- a/pkg/services/ngalert/models/testing.go +++ b/pkg/services/ngalert/models/testing.go @@ -9,7 +9,6 @@ import ( "testing" "time" - "github.com/google/uuid" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/prometheus/common/model" "github.com/stretchr/testify/require" @@ -20,222 +19,312 @@ import ( "github.com/grafana/grafana/pkg/util" ) -type AlertRuleMutator func(*AlertRule) +var ( + RuleMuts = AlertRuleMutators{} + NSMuts = NotificationSettingsMutators{} + RuleGen = &AlertRuleGenerator{ + mutators: []AlertRuleMutator{ + RuleMuts.WithUniqueUID(), RuleMuts.WithUniqueTitle(), + }, + } +) -// AlertRuleGen provides a factory function that generates a random AlertRule. -// The mutators arguments allows changing fields of the resulting structure -func AlertRuleGen(mutators ...AlertRuleMutator) func() *AlertRule { - return func() *AlertRule { - randNoDataState := func() NoDataState { - s := [...]NoDataState{ - Alerting, - NoData, - OK, - } - return s[rand.Intn(len(s))] - } +type AlertRuleMutator func(r *AlertRule) - randErrState := func() ExecutionErrorState { - s := [...]ExecutionErrorState{ - AlertingErrState, - ErrorErrState, - OkErrState, - } - return s[rand.Intn(len(s))] - } +type AlertRuleGenerator struct { + AlertRuleMutators + mutators []AlertRuleMutator +} - interval := (rand.Int63n(6) + 1) * 10 - forInterval := time.Duration(interval*rand.Int63n(6)) * time.Second - - var annotations map[string]string = nil - if rand.Int63()%2 == 0 { - annotations = GenerateAlertLabels(rand.Intn(5), "ann-") - } - var labels map[string]string = nil - if rand.Int63()%2 == 0 { - labels = GenerateAlertLabels(rand.Intn(5), "lbl-") - } - - var dashUID *string = nil - var panelID *int64 = nil - if rand.Int63()%2 == 0 { - d := util.GenerateShortUID() - dashUID = &d - p := rand.Int63n(1500) - panelID = &p - } - - var ns []NotificationSettings - if rand.Int63()%2 == 0 { - ns = append(ns, NotificationSettingsGen()()) - } - - rule := &AlertRule{ - ID: 0, - OrgID: rand.Int63n(1500) + 1, // Prevent OrgID=0 as this does not pass alert rule validation. - Title: "TEST-ALERT-" + util.GenerateShortUID(), - Condition: "A", - Data: []AlertQuery{GenerateAlertQuery()}, - Updated: time.Now().Add(-time.Duration(rand.Intn(100) + 1)), - 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: "TEST-GROUP-" + util.GenerateShortUID(), - RuleGroupIndex: rand.Intn(1500), - NoDataState: randNoDataState(), - ExecErrState: randErrState(), - For: forInterval, - Annotations: annotations, - Labels: labels, - NotificationSettings: ns, - } - - for _, mutator := range mutators { - mutator(rule) - } - return rule +func (g *AlertRuleGenerator) With(mutators ...AlertRuleMutator) *AlertRuleGenerator { + return &AlertRuleGenerator{ + AlertRuleMutators: g.AlertRuleMutators, + mutators: append(g.mutators, mutators...), } } -func WithNotEmptyLabels(count int, prefix string) AlertRuleMutator { +func (g *AlertRuleGenerator) Generate() AlertRule { + randNoDataState := func() NoDataState { + s := [...]NoDataState{ + Alerting, + NoData, + OK, + } + return s[rand.Intn(len(s))] + } + + randErrState := func() ExecutionErrorState { + s := [...]ExecutionErrorState{ + AlertingErrState, + ErrorErrState, + OkErrState, + } + return s[rand.Intn(len(s))] + } + + interval := (rand.Int63n(6) + 1) * 10 + forInterval := time.Duration(interval*rand.Int63n(6)) * time.Second + + var annotations map[string]string = nil + if rand.Int63()%2 == 0 { + annotations = GenerateAlertLabels(rand.Intn(5), "ann-") + } + var labels map[string]string = nil + if rand.Int63()%2 == 0 { + labels = GenerateAlertLabels(rand.Intn(5), "lbl-") + } + + var dashUID *string = nil + var panelID *int64 = nil + if rand.Int63()%2 == 0 { + d := util.GenerateShortUID() + dashUID = &d + p := rand.Int63n(1500) + panelID = &p + } + + var ns []NotificationSettings + if rand.Int63()%2 == 0 { + ns = append(ns, NotificationSettingsGen()()) + } + + rule := AlertRule{ + ID: 0, + 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)), + 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, + } + + for _, mutator := range g.mutators { + mutator(&rule) + } + return rule +} + +func (g *AlertRuleGenerator) GenerateRef() *AlertRule { + r := g.Generate() + return &r +} + +func (g *AlertRuleGenerator) getCount(bounds ...int) int { + count := 0 + if len(bounds) == 0 { + count = rand.Intn(5) + 1 + } + if len(bounds) == 1 { + count = bounds[0] + } + if len(bounds) == 2 { + if bounds[0] > bounds[1] { + panic("min should not be greater than max") + } else if bounds[0] < bounds[1] { + count = rand.Intn(bounds[1]-bounds[0]) + bounds[0] + } else { + count = bounds[0] + } + } + if len(bounds) > 2 { + panic("invalid number of parameter must be up to 2") + } + return count +} + +func (g *AlertRuleGenerator) GenerateMany(bounds ...int) []AlertRule { + count := g.getCount(bounds...) + result := make([]AlertRule, 0, count) + for i := 0; i < count; i++ { + result = append(result, g.Generate()) + } + return result +} + +func (g *AlertRuleGenerator) GenerateManyRef(bounds ...int) []*AlertRule { + count := g.getCount(bounds...) + + result := make([]*AlertRule, 0) + for i := 0; i < count; i++ { + r := g.Generate() + result = append(result, &r) + } + return result +} + +type AlertRuleMutators struct { +} + +func (a *AlertRuleMutators) WithNotEmptyLabels(count int, prefix string) AlertRuleMutator { return func(rule *AlertRule) { rule.Labels = GenerateAlertLabels(count, prefix) } } -func WithUniqueID() AlertRuleMutator { - usedID := make(map[int64]struct{}) +func (a *AlertRuleMutators) WithUniqueID() AlertRuleMutator { + ids := sync.Map{} return func(rule *AlertRule) { + id := rule.ID for { - id := rand.Int63n(1500) + 1 - if _, ok := usedID[id]; !ok { - usedID[id] = struct{}{} + _, exists := ids.LoadOrStore(id, struct{}{}) + if !exists { rule.ID = id return } + id = rand.Int63n(1500) + 1 } } } -func WithGroupIndex(groupIndex int) AlertRuleMutator { +func (a *AlertRuleMutators) WithGroupIndex(groupIndex int) AlertRuleMutator { return func(rule *AlertRule) { rule.RuleGroupIndex = groupIndex } } -func WithUniqueGroupIndex() AlertRuleMutator { - usedIdx := make(map[int]struct{}) +func (a *AlertRuleMutators) WithUniqueGroupIndex() AlertRuleMutator { + usedIdx := sync.Map{} return func(rule *AlertRule) { + idx := rule.RuleGroupIndex for { - idx := rand.Int() - if _, ok := usedIdx[idx]; !ok { - usedIdx[idx] = struct{}{} + if _, exists := usedIdx.LoadOrStore(idx, struct{}{}); !exists { rule.RuleGroupIndex = idx return } + idx = rand.Int() } } } -func WithSequentialGroupIndex() AlertRuleMutator { +func (a *AlertRuleMutators) WithSequentialGroupIndex() AlertRuleMutator { idx := 1 + m := sync.Mutex{} return func(rule *AlertRule) { + m.Lock() + defer m.Unlock() rule.RuleGroupIndex = idx idx++ } } -func WithOrgID(orgId int64) AlertRuleMutator { +func (a *AlertRuleMutators) WithOrgID(orgId int64) AlertRuleMutator { return func(rule *AlertRule) { rule.OrgID = orgId } } -func WithUniqueOrgID() AlertRuleMutator { - orgs := map[int64]struct{}{} +func (a *AlertRuleMutators) WithUniqueOrgID() AlertRuleMutator { + orgs := sync.Map{} return func(rule *AlertRule) { - var orgID int64 + orgID := rule.OrgID for { - orgID = rand.Int63() - if _, ok := orgs[orgID]; !ok { - break + if _, exist := orgs.LoadOrStore(orgID, struct{}{}); !exist { + rule.OrgID = orgID + return } + orgID = rand.Int63() } - orgs[orgID] = struct{}{} - rule.OrgID = orgID } } // WithNamespaceUIDNotIn generates a random namespace UID if it is among excluded -func WithNamespaceUIDNotIn(exclude ...string) AlertRuleMutator { +func (a *AlertRuleMutators) WithNamespaceUIDNotIn(exclude ...string) AlertRuleMutator { return func(rule *AlertRule) { for { if !slices.Contains(exclude, rule.NamespaceUID) { return } - rule.NamespaceUID = uuid.NewString() + rule.NamespaceUID = util.GenerateShortUID() } } } -func WithNamespace(namespace *folder.Folder) AlertRuleMutator { +func (a *AlertRuleMutators) WithNamespaceUID(namespaceUID string) AlertRuleMutator { return func(rule *AlertRule) { - rule.NamespaceUID = namespace.UID + rule.NamespaceUID = namespaceUID } } -func WithInterval(interval time.Duration) AlertRuleMutator { +func (a *AlertRuleMutators) WithNamespace(namespace *folder.Folder) AlertRuleMutator { + return a.WithNamespaceUID(namespace.UID) +} + +func (a *AlertRuleMutators) WithInterval(interval time.Duration) AlertRuleMutator { return func(rule *AlertRule) { rule.IntervalSeconds = int64(interval.Seconds()) } } -func WithIntervalBetween(min, max int64) AlertRuleMutator { +func (a *AlertRuleMutators) WithIntervalSeconds(seconds int64) AlertRuleMutator { + return func(rule *AlertRule) { + rule.IntervalSeconds = seconds + } +} + +// WithIntervalMatching mutator that generates random interval and `for` duration that are times of the provided base interval. +func (a *AlertRuleMutators) WithIntervalMatching(baseInterval time.Duration) AlertRuleMutator { + return func(rule *AlertRule) { + rule.IntervalSeconds = int64(baseInterval.Seconds()) * (rand.Int63n(10) + 1) + rule.For = time.Duration(rule.IntervalSeconds*rand.Int63n(9)+1) * time.Second + } +} + +func (a *AlertRuleMutators) WithIntervalBetween(min, max int64) AlertRuleMutator { return func(rule *AlertRule) { rule.IntervalSeconds = rand.Int63n(max-min) + min } } -func WithTitle(title string) AlertRuleMutator { +func (a *AlertRuleMutators) WithTitle(title string) AlertRuleMutator { return func(rule *AlertRule) { rule.Title = title } } -func WithFor(duration time.Duration) AlertRuleMutator { +func (a *AlertRuleMutators) WithFor(duration time.Duration) AlertRuleMutator { return func(rule *AlertRule) { rule.For = duration } } -func WithForNTimes(timesOfInterval int64) AlertRuleMutator { +func (a *AlertRuleMutators) WithForNTimes(timesOfInterval int64) AlertRuleMutator { return func(rule *AlertRule) { rule.For = time.Duration(rule.IntervalSeconds*timesOfInterval) * time.Second } } -func WithNoDataExecAs(nodata NoDataState) AlertRuleMutator { +func (a *AlertRuleMutators) WithNoDataExecAs(nodata NoDataState) AlertRuleMutator { return func(rule *AlertRule) { rule.NoDataState = nodata } } -func WithErrorExecAs(err ExecutionErrorState) AlertRuleMutator { +func (a *AlertRuleMutators) WithErrorExecAs(err ExecutionErrorState) AlertRuleMutator { return func(rule *AlertRule) { rule.ExecErrState = err } } -func WithAnnotations(a data.Labels) AlertRuleMutator { +func (a *AlertRuleMutators) WithAnnotations(lbls data.Labels) AlertRuleMutator { return func(rule *AlertRule) { - rule.Annotations = a + rule.Annotations = lbls } } -func WithAnnotation(key, value string) AlertRuleMutator { +func (a *AlertRuleMutators) WithAnnotation(key, value string) AlertRuleMutator { return func(rule *AlertRule) { if rule.Annotations == nil { rule.Annotations = data.Labels{} @@ -244,13 +333,13 @@ func WithAnnotation(key, value string) AlertRuleMutator { } } -func WithLabels(a data.Labels) AlertRuleMutator { +func (a *AlertRuleMutators) WithLabels(lbls data.Labels) AlertRuleMutator { return func(rule *AlertRule) { - rule.Labels = a + rule.Labels = lbls } } -func WithLabel(key, value string) AlertRuleMutator { +func (a *AlertRuleMutators) WithLabel(key, value string) AlertRuleMutator { return func(rule *AlertRule) { if rule.Labels == nil { rule.Labels = data.Labels{} @@ -259,35 +348,80 @@ func WithLabel(key, value string) AlertRuleMutator { } } -func WithUniqueUID(knownUids *sync.Map) AlertRuleMutator { +func (a *AlertRuleMutators) WithDashboardAndPanel(dashboardUID *string, panelID *int64) AlertRuleMutator { + return func(rule *AlertRule) { + rule.DashboardUID = dashboardUID + rule.PanelID = panelID + } +} + +// WithUniqueUID returns AlertRuleMutator that generates a random UID if it is among UIDs known by the instance of mutator. +// NOTE: two instances of the mutator do not share known UID. +// Example #1 reuse mutator instance: +// +// mut := WithUniqueUID() +// rule1 := RuleGen.With(mut).Generate() +// rule2 := RuleGen.With(mut).Generate() +// +// Example #2 reuse generator: +// +// gen := RuleGen.With(WithUniqueUID()) +// rule1 := gen.Generate() +// rule2 := gen.Generate() +// +// Example #3 non-unique: +// +// rule1 := RuleGen.With(WithUniqueUID()).Generate +// rule2 := RuleGen.With(WithUniqueUID()).Generate +func (a *AlertRuleMutators) WithUniqueUID() AlertRuleMutator { + uids := sync.Map{} return func(rule *AlertRule) { uid := rule.UID for { - _, ok := knownUids.LoadOrStore(uid, struct{}{}) - if !ok { + _, exist := uids.LoadOrStore(uid, struct{}{}) + if !exist { rule.UID = uid return } - uid = uuid.NewString() + uid = util.GenerateShortUID() } } } -func WithUniqueTitle(knownTitles *sync.Map) AlertRuleMutator { +// WithUniqueTitle returns AlertRuleMutator that generates a random title if the rule's title is among titles known by the instance of mutator. +// Two instances of the mutator do not share known titles. +// Example #1 reuse mutator instance: +// +// mut := WithUniqueTitle() +// rule1 := RuleGen.With(mut).Generate() +// rule2 := RuleGen.With(mut).Generate() +// +// Example #2 reuse generator: +// +// gen := RuleGen.With(WithUniqueTitle()) +// rule1 := gen.Generate() +// rule2 := gen.Generate() +// +// Example #3 non-unique: +// +// rule1 := RuleGen.With(WithUniqueTitle()).Generate +// rule2 := RuleGen.With(WithUniqueTitle()).Generate +func (a *AlertRuleMutators) WithUniqueTitle() AlertRuleMutator { + titles := sync.Map{} return func(rule *AlertRule) { title := rule.Title for { - _, ok := knownTitles.LoadOrStore(title, struct{}{}) - if !ok { + _, exist := titles.LoadOrStore(title, struct{}{}) + if !exist { rule.Title = title return } - title = uuid.NewString() + title = fmt.Sprintf("title-%s", util.GenerateShortUID()) } } } -func WithQuery(query ...AlertQuery) AlertRuleMutator { +func (a *AlertRuleMutators) WithQuery(query ...AlertQuery) AlertRuleMutator { return func(rule *AlertRule) { rule.Data = query if len(query) > 1 { @@ -296,7 +430,19 @@ func WithQuery(query ...AlertQuery) AlertRuleMutator { } } -func WithGroupKey(groupKey AlertRuleGroupKey) AlertRuleMutator { +func (a *AlertRuleMutators) WithGroupName(groupName string) AlertRuleMutator { + return func(rule *AlertRule) { + rule.RuleGroup = groupName + } +} + +func (a *AlertRuleMutators) WithGroupPrefix(prefix string) AlertRuleMutator { + return func(rule *AlertRule) { + rule.RuleGroup = fmt.Sprintf("%s%s", prefix, util.GenerateShortUID()) + } +} + +func (a *AlertRuleMutators) WithGroupKey(groupKey AlertRuleGroupKey) AlertRuleMutator { return func(rule *AlertRule) { rule.RuleGroup = groupKey.RuleGroup rule.OrgID = groupKey.OrgID @@ -304,19 +450,48 @@ func WithGroupKey(groupKey AlertRuleGroupKey) AlertRuleMutator { } } -func WithNotificationSettingsGen(ns func() NotificationSettings) AlertRuleMutator { +// WithSameGroup generates a random group name and assigns it to all rules passed to it +func (a *AlertRuleMutators) WithSameGroup() AlertRuleMutator { + once := sync.Once{} + name := "" + return func(rule *AlertRule) { + once.Do(func() { + name = util.GenerateShortUID() + }) + rule.RuleGroup = name + } +} + +func (a *AlertRuleMutators) WithNotificationSettingsGen(ns func() NotificationSettings) AlertRuleMutator { return func(rule *AlertRule) { rule.NotificationSettings = []NotificationSettings{ns()} } } +func (a *AlertRuleMutators) WithNotificationSettings(ns NotificationSettings) AlertRuleMutator { + return func(rule *AlertRule) { + rule.NotificationSettings = []NotificationSettings{ns} + } +} -func WithNoNotificationSettings() AlertRuleMutator { +func (a *AlertRuleMutators) WithNoNotificationSettings() AlertRuleMutator { return func(rule *AlertRule) { rule.NotificationSettings = nil } } -func GenerateAlertLabels(count int, prefix string) data.Labels { +func (a *AlertRuleMutators) WithIsPaused(paused bool) AlertRuleMutator { + return func(rule *AlertRule) { + rule.IsPaused = paused + } +} + +func (g *AlertRuleGenerator) GenerateLabels(min, max int, prefix string) data.Labels { + count := max + if min > max { + panic("min should not be greater than max") + } else if min < max { + count = rand.Intn(max-min) + min + } labels := make(data.Labels, count) for i := 0; i < count; i++ { labels[prefix+"key-"+util.GenerateShortUID()] = prefix + "value-" + util.GenerateShortUID() @@ -324,7 +499,15 @@ func GenerateAlertLabels(count int, prefix string) data.Labels { return labels } +func GenerateAlertLabels(count int, prefix string) data.Labels { + return RuleGen.GenerateLabels(count, count, prefix) +} + func GenerateAlertQuery() AlertQuery { + return RuleGen.GenerateQuery() +} + +func (g *AlertRuleGenerator) GenerateQuery() AlertQuery { f := rand.Intn(10) + 5 t := rand.Intn(f) @@ -343,35 +526,10 @@ func GenerateAlertQuery() AlertQuery { } } -// GenerateUniqueAlertRules generates many random alert rules and makes sure that they have unique UID. -// It returns a tuple where first element is a map where keys are UID of alert rule and the second element is a slice of the same rules -func GenerateUniqueAlertRules(count int, f func() *AlertRule) (map[string]*AlertRule, []*AlertRule) { - uIDs := make(map[string]*AlertRule, count) - result := make([]*AlertRule, 0, count) - for len(result) < count { - rule := f() - if _, ok := uIDs[rule.UID]; ok { - continue - } - result = append(result, rule) - uIDs[rule.UID] = rule +func (g *AlertRuleGenerator) WithCondition(condition string) AlertRuleMutator { + return func(r *AlertRule) { + r.Condition = condition } - return uIDs, result -} - -// GenerateAlertRulesSmallNonEmpty generates 1 to 5 rules using the provided generator -func GenerateAlertRulesSmallNonEmpty(f func() *AlertRule) []*AlertRule { - return GenerateAlertRules(rand.Intn(4)+1, f) -} - -// GenerateAlertRules generates many random alert rules. Does not guarantee that rules are unique (by UID) -func GenerateAlertRules(count int, f func() *AlertRule) []*AlertRule { - result := make([]*AlertRule, 0, count) - for len(result) < count { - rule := f() - result = append(result, rule) - } - return result } // GenerateRuleKey generates a random alert rule key @@ -392,7 +550,7 @@ func GenerateGroupKey(orgID int64) AlertRuleGroupKey { } // CopyRule creates a deep copy of AlertRule -func CopyRule(r *AlertRule) *AlertRule { +func CopyRule(r *AlertRule, mutators ...AlertRuleMutator) *AlertRule { result := AlertRule{ ID: r.ID, OrgID: r.OrgID, @@ -449,6 +607,12 @@ func CopyRule(r *AlertRule) *AlertRule { result.NotificationSettings = append(result.NotificationSettings, CopyNotificationSettings(s)) } + if len(mutators) > 0 { + for _, mutator := range mutators { + mutator(&result) + } + } + return &result } @@ -687,10 +851,6 @@ func NotificationSettingsGen(mutators ...Mutator[NotificationSettings]) func() N } } -var ( - NSMuts = NotificationSettingsMutators{} -) - type NotificationSettingsMutators struct{} func (n NotificationSettingsMutators) WithReceiver(receiver string) Mutator[NotificationSettings] { diff --git a/pkg/services/ngalert/ngalert_test.go b/pkg/services/ngalert/ngalert_test.go index b717f562cb1..ed7bee52d7c 100644 --- a/pkg/services/ngalert/ngalert_test.go +++ b/pkg/services/ngalert/ngalert_test.go @@ -29,7 +29,8 @@ func Test_subscribeToFolderChanges(t *testing.T) { UID: util.GenerateShortUID(), Title: "Folder" + util.GenerateShortUID(), } - rules := models.GenerateAlertRules(5, models.AlertRuleGen(models.WithOrgID(orgID), models.WithNamespace(folder))) + gen := models.RuleGen + rules := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder)).GenerateManyRef(5) bus := bus.ProvideBus(tracing.InitializeTracerForTest()) db := fakes.NewRuleStore(t) diff --git a/pkg/services/ngalert/provisioning/accesscontrol_test.go b/pkg/services/ngalert/provisioning/accesscontrol_test.go index 04d9000c0ec..a55e44429b2 100644 --- a/pkg/services/ngalert/provisioning/accesscontrol_test.go +++ b/pkg/services/ngalert/provisioning/accesscontrol_test.go @@ -84,7 +84,7 @@ func TestCanWriteAllRules(t *testing.T) { func TestAuthorizeAccessToRuleGroup(t *testing.T) { testUser := &user.SignedInUser{} - rules := models.GenerateAlertRules(1, models.AlertRuleGen()) + rules := models.RuleGen.GenerateManyRef(1) t.Run("should return nil when user has provisioning permissions", func(t *testing.T) { rs := &fakes.FakeRuleService{} diff --git a/pkg/services/ngalert/provisioning/alert_rules_test.go b/pkg/services/ngalert/provisioning/alert_rules_test.go index fea81b19053..d035f5b78bf 100644 --- a/pkg/services/ngalert/provisioning/alert_rules_test.go +++ b/pkg/services/ngalert/provisioning/alert_rules_test.go @@ -551,7 +551,8 @@ func TestCreateAlertRule(t *testing.T) { u := &user.SignedInUser{OrgID: orgID} groupKey := models.GenerateGroupKey(orgID) groupIntervalSeconds := int64(30) - rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(groupIntervalSeconds)*time.Second))) + gen := models.RuleGen + rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(groupIntervalSeconds)).GenerateManyRef(3) groupProvenance := models.ProvenanceAPI initServiceWithData := func(t *testing.T) (*AlertRuleService, *fakes.RuleStore, *fakes.FakeProvisioningStore, *fakeRuleAccessControlService) { @@ -567,14 +568,14 @@ func TestCreateAlertRule(t *testing.T) { t.Run("when user can write all rules", func(t *testing.T) { t.Run("and a new rule creates a new group", func(t *testing.T) { - rule := models.AlertRuleGen(models.WithOrgID(orgID))() + rule := gen.With(gen.WithOrgID(orgID)).Generate() service, ruleStore, provenanceStore, ac := initServiceWithData(t) ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) { return true, nil } - actualRule, err := service.CreateAlertRule(context.Background(), u, *rule, models.ProvenanceFile) + actualRule, err := service.CreateAlertRule(context.Background(), u, rule, models.ProvenanceFile) require.NoError(t, err) require.Len(t, ac.Calls, 1) @@ -601,14 +602,14 @@ func TestCreateAlertRule(t *testing.T) { }) }) t.Run("and it adds a rule to a group", func(t *testing.T) { - rule := models.AlertRuleGen(models.WithGroupKey(groupKey))() + rule := gen.With(gen.WithGroupKey(groupKey)).Generate() service, ruleStore, provenanceStore, ac := initServiceWithData(t) ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) { return true, nil } - actualRule, err := service.CreateAlertRule(context.Background(), u, *rule, models.ProvenanceNone) + actualRule, err := service.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone) require.NoError(t, err) require.Len(t, ac.Calls, 1) @@ -637,7 +638,7 @@ func TestCreateAlertRule(t *testing.T) { }) t.Run("when user cannot write all rules", func(t *testing.T) { t.Run("and it creates a new group", func(t *testing.T) { - rule := models.AlertRuleGen(models.WithOrgID(orgID))() + rule := gen.With(gen.WithOrgID(orgID)).Generate() t.Run("it should authorize the change", func(t *testing.T) { service, ruleStore, provenanceStore, ac := initServiceWithData(t) @@ -654,7 +655,7 @@ func TestCreateAlertRule(t *testing.T) { return nil } - actualRule, err := service.CreateAlertRule(context.Background(), u, *rule, models.ProvenanceFile) + actualRule, err := service.CreateAlertRule(context.Background(), u, rule, models.ProvenanceFile) require.NoError(t, err) require.Len(t, ac.Calls, 2) @@ -683,7 +684,7 @@ func TestCreateAlertRule(t *testing.T) { }) }) t.Run("and it adds a rule to a group", func(t *testing.T) { - rule := models.AlertRuleGen(models.WithGroupKey(groupKey))() + rule := gen.With(gen.WithGroupKey(groupKey)).Generate() t.Run("it should authorize the change to whole group", func(t *testing.T) { service, ruleStore, provenanceStore, ac := initServiceWithData(t) @@ -701,7 +702,7 @@ func TestCreateAlertRule(t *testing.T) { return nil } - actualRule, err := service.CreateAlertRule(context.Background(), u, *rule, models.ProvenanceNone) + actualRule, err := service.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone) require.NoError(t, err) require.Len(t, ac.Calls, 2) @@ -730,7 +731,7 @@ func TestCreateAlertRule(t *testing.T) { }) }) t.Run("it should not insert if not authorized", func(t *testing.T) { - rule := models.AlertRuleGen(models.WithGroupKey(groupKey))() + rule := gen.With(gen.WithGroupKey(groupKey)).Generate() service, ruleStore, _, ac := initServiceWithData(t) ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) { @@ -741,7 +742,7 @@ func TestCreateAlertRule(t *testing.T) { return expectedErr } - _, err := service.CreateAlertRule(context.Background(), u, *rule, models.ProvenanceFile) + _, err := service.CreateAlertRule(context.Background(), u, rule, models.ProvenanceFile) require.ErrorIs(t, expectedErr, err) require.Len(t, ac.Calls, 2) @@ -797,7 +798,8 @@ func TestUpdateAlertRule(t *testing.T) { u := &user.SignedInUser{OrgID: orgID} groupKey := models.GenerateGroupKey(orgID) groupIntervalSeconds := int64(30) - rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(groupIntervalSeconds)*time.Second))) + gen := models.RuleGen + rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(groupIntervalSeconds)).GenerateManyRef(3) groupProvenance := models.ProvenanceAPI initServiceWithData := func(t *testing.T) (*AlertRuleService, *fakes.RuleStore, *fakes.FakeProvisioningStore, *fakeRuleAccessControlService) { @@ -899,7 +901,8 @@ func TestDeleteAlertRule(t *testing.T) { u := &user.SignedInUser{OrgID: orgID} groupKey := models.GenerateGroupKey(orgID) groupIntervalSeconds := int64(30) - rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(groupIntervalSeconds)*time.Second))) + gen := models.RuleGen + rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(groupIntervalSeconds)).GenerateManyRef(3) groupProvenance := models.ProvenanceAPI initServiceWithData := func(t *testing.T) (*AlertRuleService, *fakes.RuleStore, *fakes.FakeProvisioningStore, *fakeRuleAccessControlService) { @@ -990,7 +993,8 @@ func TestGetAlertRule(t *testing.T) { orgID := rand.Int63() u := &user.SignedInUser{OrgID: orgID} groupKey := models.GenerateGroupKey(orgID) - rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey))) + gen := models.RuleGen + rules := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(3) rule := rules[0] expectedProvenance := models.ProvenanceAPI @@ -1118,7 +1122,8 @@ func TestGetRuleGroup(t *testing.T) { u := &user.SignedInUser{OrgID: orgID} groupKey := models.GenerateGroupKey(orgID) intervalSeconds := int64(30) - rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(intervalSeconds)*time.Second))) + gen := models.RuleGen + rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(intervalSeconds)).GenerateManyRef(3) derefRules := make([]models.AlertRule, 0, len(rules)) for _, rule := range rules { derefRules = append(derefRules, *rule) @@ -1226,9 +1231,10 @@ func TestGetAlertRules(t *testing.T) { u := &user.SignedInUser{OrgID: orgID} groupKey1 := models.GenerateGroupKey(orgID) groupKey2 := models.GenerateGroupKey(orgID) - rules1 := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey1))) + gen := models.RuleGen + rules1 := gen.With(gen.WithGroupKey(groupKey1), gen.WithUniqueGroupIndex()).GenerateManyRef(3) models.RulesGroup(rules1).SortByGroupIndex() - rules2 := models.GenerateAlertRules(4, models.AlertRuleGen(models.WithGroupKey(groupKey2))) + rules2 := gen.With(gen.WithGroupKey(groupKey2), gen.WithUniqueGroupIndex()).GenerateManyRef(4) models.RulesGroup(rules2).SortByGroupIndex() allRules := append(rules1, rules2...) expectedProvenance := models.ProvenanceAPI @@ -1325,7 +1331,8 @@ func TestReplaceGroup(t *testing.T) { u := &user.SignedInUser{OrgID: orgID} groupKey := models.GenerateGroupKey(orgID) groupIntervalSeconds := int64(30) - rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(groupIntervalSeconds)*time.Second))) + gen := models.RuleGen + rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(groupIntervalSeconds)).GenerateManyRef(3) groupProvenance := models.ProvenanceAPI initServiceWithData := func(t *testing.T) (*AlertRuleService, *fakes.RuleStore, *fakes.FakeProvisioningStore, *fakeRuleAccessControlService) { @@ -1438,7 +1445,8 @@ func TestDeleteRuleGroup(t *testing.T) { u := &user.SignedInUser{OrgID: orgID} groupKey := models.GenerateGroupKey(orgID) groupIntervalSeconds := int64(30) - rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey), models.WithInterval(time.Duration(groupIntervalSeconds)*time.Second))) + gen := models.RuleGen + rules := gen.With(gen.WithGroupKey(groupKey), gen.WithIntervalSeconds(groupIntervalSeconds)).GenerateManyRef(3) groupProvenance := models.ProvenanceAPI initServiceWithData := func(t *testing.T) (*AlertRuleService, *fakes.RuleStore, *fakes.FakeProvisioningStore, *fakeRuleAccessControlService) { diff --git a/pkg/services/ngalert/schedule/alert_rule_test.go b/pkg/services/ngalert/schedule/alert_rule_test.go index f910376cfba..3eb82f141e1 100644 --- a/pkg/services/ngalert/schedule/alert_rule_test.go +++ b/pkg/services/ngalert/schedule/alert_rule_test.go @@ -13,20 +13,22 @@ import ( alertingModels "github.com/grafana/alerting/models" "github.com/grafana/grafana-plugin-sdk-go/data" - definitions "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" - "github.com/grafana/grafana/pkg/services/ngalert/eval" - models "github.com/grafana/grafana/pkg/services/ngalert/models" - "github.com/grafana/grafana/pkg/services/ngalert/state" - "github.com/grafana/grafana/pkg/util" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" prometheusModel "github.com/prometheus/common/model" "github.com/stretchr/testify/assert" mock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + + definitions "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/ngalert/eval" + models "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/state" + "github.com/grafana/grafana/pkg/util" ) func TestAlertRule(t *testing.T) { + gen := models.RuleGen type evalResponse struct { success bool droppedEval *Evaluation @@ -78,7 +80,7 @@ func TestAlertRule(t *testing.T) { resultCh := make(chan evalResponse) data := &Evaluation{ scheduledAt: expected, - rule: models.AlertRuleGen()(), + rule: gen.GenerateRef(), folderTitle: util.GenerateShortUID(), } go func() { @@ -103,7 +105,7 @@ func TestAlertRule(t *testing.T) { resultCh2 := make(chan evalResponse) data := &Evaluation{ scheduledAt: time1, - rule: models.AlertRuleGen()(), + rule: gen.GenerateRef(), folderTitle: util.GenerateShortUID(), } data2 := &Evaluation{ @@ -146,7 +148,7 @@ func TestAlertRule(t *testing.T) { resultCh := make(chan evalResponse) data := &Evaluation{ scheduledAt: time.Now(), - rule: models.AlertRuleGen()(), + rule: gen.GenerateRef(), folderTitle: util.GenerateShortUID(), } go func() { @@ -176,7 +178,7 @@ func TestAlertRule(t *testing.T) { r.Stop(nil) data := &Evaluation{ scheduledAt: time.Now(), - rule: models.AlertRuleGen()(), + rule: gen.GenerateRef(), folderTitle: util.GenerateShortUID(), } success, dropped := r.Eval(data) @@ -225,7 +227,7 @@ func TestAlertRule(t *testing.T) { case 2: r.Eval(&Evaluation{ scheduledAt: time.Now(), - rule: models.AlertRuleGen()(), + rule: gen.GenerateRef(), folderTitle: util.GenerateShortUID(), }) case 3: @@ -245,6 +247,7 @@ func blankRuleForTests(ctx context.Context) *alertRule { } func TestRuleRoutine(t *testing.T) { + gen := models.RuleGen createSchedule := func( evalAppliedChan chan time.Time, senderMock *SyncAlertsSenderMock, @@ -270,7 +273,7 @@ func TestRuleRoutine(t *testing.T) { evalAppliedChan := make(chan time.Time) sch, ruleStore, instanceStore, reg := createSchedule(evalAppliedChan, nil) - rule := models.AlertRuleGen(withQueryForState(t, evalState))() + rule := gen.With(withQueryForState(t, evalState)).GenerateRef() ruleStore.PutRule(context.Background(), rule) folderTitle := ruleStore.getNamespaceTitle(rule.NamespaceUID) factory := ruleFactoryFromScheduler(sch) @@ -432,7 +435,7 @@ func TestRuleRoutine(t *testing.T) { stoppedChan := make(chan error) sch, _, _, _ := createSchedule(make(chan time.Time), nil) - rule := models.AlertRuleGen()() + rule := gen.GenerateRef() _ = sch.stateManager.ProcessEvalResults(context.Background(), sch.clock.Now(), rule, eval.GenerateResults(rand.Intn(5)+1, eval.ResultGen(eval.WithEvaluatedAt(sch.clock.Now()))), nil) expectedStates := sch.stateManager.GetStatesForRuleUID(rule.OrgID, rule.UID) require.NotEmpty(t, expectedStates) @@ -454,7 +457,7 @@ func TestRuleRoutine(t *testing.T) { stoppedChan := make(chan error) sch, _, _, _ := createSchedule(make(chan time.Time), nil) - rule := models.AlertRuleGen()() + rule := gen.GenerateRef() _ = sch.stateManager.ProcessEvalResults(context.Background(), sch.clock.Now(), rule, eval.GenerateResults(rand.Intn(5)+1, eval.ResultGen(eval.WithEvaluatedAt(sch.clock.Now()))), nil) require.NotEmpty(t, sch.stateManager.GetStatesForRuleUID(rule.OrgID, rule.UID)) @@ -474,7 +477,7 @@ func TestRuleRoutine(t *testing.T) { }) t.Run("when a message is sent to update channel", func(t *testing.T) { - rule := models.AlertRuleGen(withQueryForState(t, eval.Normal))() + rule := gen.With(withQueryForState(t, eval.Normal)).GenerateRef() folderTitle := "folderName" ruleFp := ruleWithFolder{rule, folderTitle}.Fingerprint() @@ -557,7 +560,7 @@ func TestRuleRoutine(t *testing.T) { }) t.Run("when evaluation fails", func(t *testing.T) { - rule := models.AlertRuleGen(withQueryForState(t, eval.Error))() + rule := gen.With(withQueryForState(t, eval.Error)).GenerateRef() rule.ExecErrState = models.ErrorErrState evalAppliedChan := make(chan time.Time) @@ -678,7 +681,7 @@ func TestRuleRoutine(t *testing.T) { t.Run("when there are alerts that should be firing", func(t *testing.T) { t.Run("it should call sender", func(t *testing.T) { // eval.Alerting makes state manager to create notifications for alertmanagers - rule := models.AlertRuleGen(withQueryForState(t, eval.Alerting))() + rule := gen.With(withQueryForState(t, eval.Alerting)).GenerateRef() evalAppliedChan := make(chan time.Time) @@ -712,7 +715,7 @@ func TestRuleRoutine(t *testing.T) { }) t.Run("when there are no alerts to send it should not call notifiers", func(t *testing.T) { - rule := models.AlertRuleGen(withQueryForState(t, eval.Normal))() + rule := gen.With(withQueryForState(t, eval.Normal)).GenerateRef() evalAppliedChan := make(chan time.Time) diff --git a/pkg/services/ngalert/schedule/jitter_test.go b/pkg/services/ngalert/schedule/jitter_test.go index ae7ead86fe7..9ffc358636c 100644 --- a/pkg/services/ngalert/schedule/jitter_test.go +++ b/pkg/services/ngalert/schedule/jitter_test.go @@ -4,14 +4,17 @@ import ( "testing" "time" - ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/stretchr/testify/require" + + ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ) func TestJitter(t *testing.T) { + gen := ngmodels.RuleGen + genWithInterval10to600 := gen.With(gen.WithIntervalBetween(10, 600)) t.Run("when strategy is JitterNever", func(t *testing.T) { t.Run("offset is always zero", func(t *testing.T) { - rules := createTestRules(100, ngmodels.WithIntervalBetween(10, 600)) + rules := genWithInterval10to600.GenerateManyRef(100) baseInterval := 10 * time.Second for _, r := range rules { @@ -23,7 +26,7 @@ func TestJitter(t *testing.T) { t.Run("when strategy is JitterByGroup", func(t *testing.T) { t.Run("offset is stable for the same rule", func(t *testing.T) { - rule := ngmodels.AlertRuleGen(ngmodels.WithIntervalBetween(10, 600))() + rule := genWithInterval10to600.GenerateRef() baseInterval := 10 * time.Second original := jitterOffsetInTicks(rule, baseInterval, JitterByGroup) @@ -35,7 +38,7 @@ func TestJitter(t *testing.T) { t.Run("offset is on the interval [0, interval/baseInterval)", func(t *testing.T) { baseInterval := 10 * time.Second - rules := createTestRules(1000, ngmodels.WithIntervalBetween(10, 600)) + rules := genWithInterval10to600.GenerateManyRef(1000) for _, r := range rules { offset := jitterOffsetInTicks(r, baseInterval, JitterByGroup) @@ -49,8 +52,8 @@ func TestJitter(t *testing.T) { baseInterval := 10 * time.Second group1 := ngmodels.AlertRuleGroupKey{} group2 := ngmodels.AlertRuleGroupKey{} - rules1 := createTestRules(1000, ngmodels.WithInterval(60*time.Second), ngmodels.WithGroupKey(group1)) - rules2 := createTestRules(1000, ngmodels.WithInterval(1*time.Hour), ngmodels.WithGroupKey(group2)) + rules1 := gen.With(gen.WithInterval(60*time.Second), gen.WithGroupKey(group1)).GenerateManyRef(1000) + rules2 := gen.With(gen.WithInterval(1*time.Hour), gen.WithGroupKey(group2)).GenerateManyRef(1000) group1Offset := jitterOffsetInTicks(rules1[0], baseInterval, JitterByGroup) for _, r := range rules1 { @@ -67,7 +70,7 @@ func TestJitter(t *testing.T) { t.Run("when strategy is JitterByRule", func(t *testing.T) { t.Run("offset is stable for the same rule", func(t *testing.T) { - rule := ngmodels.AlertRuleGen(ngmodels.WithIntervalBetween(10, 600))() + rule := genWithInterval10to600.GenerateRef() baseInterval := 10 * time.Second original := jitterOffsetInTicks(rule, baseInterval, JitterByRule) @@ -79,7 +82,7 @@ func TestJitter(t *testing.T) { t.Run("offset is on the interval [0, interval/baseInterval)", func(t *testing.T) { baseInterval := 10 * time.Second - rules := createTestRules(1000, ngmodels.WithIntervalBetween(10, 600)) + rules := genWithInterval10to600.GenerateManyRef(1000) for _, r := range rules { offset := jitterOffsetInTicks(r, baseInterval, JitterByRule) @@ -90,11 +93,3 @@ func TestJitter(t *testing.T) { }) }) } - -func createTestRules(n int, mutators ...ngmodels.AlertRuleMutator) []*ngmodels.AlertRule { - result := make([]*ngmodels.AlertRule, 0, n) - for i := 0; i < n; i++ { - result = append(result, ngmodels.AlertRuleGen(mutators...)()) - } - return result -} diff --git a/pkg/services/ngalert/schedule/loaded_metrics_reader_test.go b/pkg/services/ngalert/schedule/loaded_metrics_reader_test.go index 14648031c8a..0ecbeb4f008 100644 --- a/pkg/services/ngalert/schedule/loaded_metrics_reader_test.go +++ b/pkg/services/ngalert/schedule/loaded_metrics_reader_test.go @@ -13,7 +13,7 @@ import ( ) func TestLoadedResultsFromRuleState(t *testing.T) { - rule := ngmodels.AlertRuleGen()() + rule := ngmodels.RuleGen.GenerateRef() p := &FakeRuleStateProvider{ map[ngmodels.AlertRuleKey][]*state.State{ rule.GetKey(): { diff --git a/pkg/services/ngalert/schedule/registry_bench_test.go b/pkg/services/ngalert/schedule/registry_bench_test.go index 7ab26df885c..2b036133702 100644 --- a/pkg/services/ngalert/schedule/registry_bench_test.go +++ b/pkg/services/ngalert/schedule/registry_bench_test.go @@ -12,12 +12,13 @@ import ( ) func BenchmarkRuleWithFolderFingerprint(b *testing.B) { - rules := models.GenerateAlertRules(b.N, models.AlertRuleGen(func(rule *models.AlertRule) { + gen := models.RuleGen + rules := gen.With(func(rule *models.AlertRule) { rule.Data = make([]models.AlertQuery, 0, 5) for i := 0; i < rand.Intn(5)+1; i++ { - rule.Data = append(rule.Data, models.GenerateAlertQuery()) + rule.Data = append(rule.Data, gen.GenerateQuery()) } - })) + }).GenerateManyRef(b.N) folder := uuid.NewString() b.ReportAllocs() b.ResetTimer() diff --git a/pkg/services/ngalert/schedule/registry_test.go b/pkg/services/ngalert/schedule/registry_test.go index c617d91a562..d7628cdd356 100644 --- a/pkg/services/ngalert/schedule/registry_test.go +++ b/pkg/services/ngalert/schedule/registry_test.go @@ -78,7 +78,8 @@ func TestSchedulableAlertRulesRegistry(t *testing.T) { } func TestSchedulableAlertRulesRegistry_set(t *testing.T) { - _, initialRules := models.GenerateUniqueAlertRules(100, models.AlertRuleGen()) + gen := models.RuleGen + initialRules := gen.GenerateManyRef(100) init := make(map[models.AlertRuleKey]*models.AlertRule, len(initialRules)) for _, rule := range initialRules { init[rule.GetKey()] = rule @@ -95,7 +96,7 @@ func TestSchedulableAlertRulesRegistry_set(t *testing.T) { t.Run("should return empty diff if version does not change", func(t *testing.T) { newRules := make([]*models.AlertRule, 0, len(initialRules)) // generate random and then override rule key + version - _, randomNew := models.GenerateUniqueAlertRules(len(initialRules), models.AlertRuleGen()) + randomNew := gen.GenerateManyRef(len(initialRules)) for i := 0; i < len(initialRules); i++ { rule := randomNew[i] oldRule := initialRules[i] @@ -128,7 +129,7 @@ func TestSchedulableAlertRulesRegistry_set(t *testing.T) { } func TestRuleWithFolderFingerprint(t *testing.T) { - rule := models.AlertRuleGen()() + rule := models.RuleGen.GenerateRef() title := uuid.NewString() f := ruleWithFolder{rule: rule, folderTitle: title}.Fingerprint() t.Run("should calculate a fingerprint", func(t *testing.T) { diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index 4808f8312b8..50258cb2247 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -101,9 +101,9 @@ func TestProcessTicks(t *testing.T) { } tick := time.Time{} - + gen := models.RuleGen // create alert rule under main org with one second interval - alertRule1 := models.AlertRuleGen(models.WithOrgID(mainOrgID), models.WithInterval(cfg.BaseInterval), models.WithTitle("rule-1"))() + alertRule1 := gen.With(gen.WithOrgID(mainOrgID), gen.WithInterval(cfg.BaseInterval), gen.WithTitle("rule-1")).GenerateRef() ruleStore.PutRule(ctx, alertRule1) t.Run("on 1st tick alert rule should be evaluated", func(t *testing.T) { @@ -132,7 +132,7 @@ func TestProcessTicks(t *testing.T) { }) // add alert rule under main org with three base intervals - alertRule2 := models.AlertRuleGen(models.WithOrgID(mainOrgID), models.WithInterval(3*cfg.BaseInterval), models.WithTitle("rule-2"))() + alertRule2 := gen.With(gen.WithOrgID(mainOrgID), gen.WithInterval(3*cfg.BaseInterval), gen.WithTitle("rule-2")).GenerateRef() ruleStore.PutRule(ctx, alertRule2) t.Run("on 2nd tick first alert rule should be evaluated", func(t *testing.T) { @@ -317,7 +317,7 @@ func TestProcessTicks(t *testing.T) { }) // create alert rule with one base interval - alertRule3 := models.AlertRuleGen(models.WithOrgID(mainOrgID), models.WithInterval(cfg.BaseInterval), models.WithTitle("rule-3"))() + alertRule3 := gen.With(gen.WithOrgID(mainOrgID), gen.WithInterval(cfg.BaseInterval), gen.WithTitle("rule-3")).GenerateRef() ruleStore.PutRule(ctx, alertRule3) t.Run("on 10th tick a new alert rule should be evaluated", func(t *testing.T) { @@ -361,7 +361,7 @@ func TestSchedule_deleteAlertRule(t *testing.T) { t.Run("it should stop evaluation loop and remove the controller from registry", func(t *testing.T) { sch := setupScheduler(t, nil, nil, nil, nil, nil) ruleFactory := ruleFactoryFromScheduler(sch) - rule := models.AlertRuleGen()() + rule := models.RuleGen.GenerateRef() key := rule.GetKey() info, _ := sch.registry.getOrCreate(context.Background(), key, ruleFactory) sch.deleteAlertRule(key) diff --git a/pkg/services/ngalert/state/cache_bench_test.go b/pkg/services/ngalert/state/cache_bench_test.go index 1373d1ae713..d8b0cdb04fa 100644 --- a/pkg/services/ngalert/state/cache_bench_test.go +++ b/pkg/services/ngalert/state/cache_bench_test.go @@ -16,7 +16,7 @@ import ( func BenchmarkGetOrCreateTest(b *testing.B) { cache := newCache() - rule := models.AlertRuleGen(func(rule *models.AlertRule) { + rule := models.RuleGen.With(func(rule *models.AlertRule) { for i := 0; i < 2; i++ { rule.Labels = data.Labels{ "label-1": "{{ $value }}", @@ -27,7 +27,7 @@ func BenchmarkGetOrCreateTest(b *testing.B) { "anno-2": "{{ $values.A.Labels.instance }} has value {{ $values.A }}", } } - })() + }).GenerateRef() result := eval.ResultGen(func(r *eval.Result) { r.Values = map[string]eval.NumberValueCapture{ "A": { diff --git a/pkg/services/ngalert/state/cache_test.go b/pkg/services/ngalert/state/cache_test.go index 58a0adbfafd..23292714226 100644 --- a/pkg/services/ngalert/state/cache_test.go +++ b/pkg/services/ngalert/state/cache_test.go @@ -126,7 +126,8 @@ func Test_getOrCreate(t *testing.T) { l := log.New("test") c := newCache() - generateRule := models.AlertRuleGen(models.WithNotEmptyLabels(5, "rule-")) + gen := models.RuleGen + generateRule := gen.With(gen.WithNotEmptyLabels(5, "rule-")).GenerateRef t.Run("should combine all labels", func(t *testing.T) { rule := generateRule() diff --git a/pkg/services/ngalert/state/historian/annotation_test.go b/pkg/services/ngalert/state/historian/annotation_test.go index 0192d321659..da09fdc333b 100644 --- a/pkg/services/ngalert/state/historian/annotation_test.go +++ b/pkg/services/ngalert/state/historian/annotation_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/annotations" @@ -128,7 +129,7 @@ func createTestAnnotationSutWithStore(t *testing.T, annotations AnnotationStore) met := metrics.NewHistorianMetrics(prometheus.NewRegistry(), metrics.Subsystem) rules := fakes.NewRuleStore(t) rules.Rules[1] = []*models.AlertRule{ - models.AlertRuleGen(withOrgID(1), withUID("my-rule"))(), + models.RuleGen.With(models.RuleMuts.WithOrgID(1), withUID("my-rule")).GenerateRef(), } return NewAnnotationBackend(annotations, rules, met) } @@ -138,7 +139,7 @@ func createTestAnnotationBackendSutWithMetrics(t *testing.T, met *metrics.Histor fakeAnnoRepo := annotationstest.NewFakeAnnotationsRepo() rules := fakes.NewRuleStore(t) rules.Rules[1] = []*models.AlertRule{ - models.AlertRuleGen(withOrgID(1), withUID("my-rule"))(), + models.RuleGen.With(models.RuleMuts.WithOrgID(1), withUID("my-rule")).GenerateRef(), } dbs := &dashboards.FakeDashboardService{} dbs.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil) @@ -150,7 +151,7 @@ func createFailingAnnotationSut(t *testing.T, met *metrics.Historian) *Annotatio fakeAnnoRepo := &failingAnnotationRepo{} rules := fakes.NewRuleStore(t) rules.Rules[1] = []*models.AlertRule{ - models.AlertRuleGen(withOrgID(1), withUID("my-rule"))(), + models.RuleGen.With(models.RuleMuts.WithOrgID(1), withUID("my-rule")).GenerateRef(), } dbs := &dashboards.FakeDashboardService{} dbs.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil) @@ -169,12 +170,6 @@ func createAnnotation() annotations.Item { } } -func withOrgID(orgId int64) func(rule *models.AlertRule) { - return func(rule *models.AlertRule) { - rule.OrgID = orgId - } -} - func TestBuildAnnotations(t *testing.T) { t.Run("data wraps nil values when values are nil", func(t *testing.T) { logger := log.NewNopLogger() @@ -230,7 +225,7 @@ func makeStateTransition() state.StateTransition { } } -func withUID(uid string) func(rule *models.AlertRule) { +func withUID(uid string) models.AlertRuleMutator { return func(rule *models.AlertRule) { rule.UID = uid } diff --git a/pkg/services/ngalert/state/manager_private_test.go b/pkg/services/ngalert/state/manager_private_test.go index 798b37a0720..39c66375219 100644 --- a/pkg/services/ngalert/state/manager_private_test.go +++ b/pkg/services/ngalert/state/manager_private_test.go @@ -146,11 +146,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { } baseRuleWith := func(mutators ...ngmodels.AlertRuleMutator) *ngmodels.AlertRule { - r := ngmodels.CopyRule(baseRule) - for _, mutator := range mutators { - mutator(r) - } - return r + return ngmodels.CopyRule(baseRule, mutators...) } newEvaluation := func(evalTime time.Time, evalState eval.State) Evaluation { @@ -397,7 +393,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { }, { desc: "t1[1:alerting,2:normal] and 'for'>0 at t1", - alertRule: baseRuleWith(ngmodels.WithForNTimes(3)), + alertRule: baseRuleWith(ngmodels.RuleMuts.WithForNTimes(3)), results: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), @@ -483,7 +479,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { }, { desc: "t1[1:alerting] t2[1:alerting] t3[1:alerting] and 'for'=2 at t1,t2,t3", - alertRule: baseRuleWith(ngmodels.WithForNTimes(2)), + alertRule: baseRuleWith(ngmodels.RuleMuts.WithForNTimes(2)), results: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), @@ -547,7 +543,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { }, { desc: "t1[1:alerting], t2[1:normal] and 'for'=2 at t2", - alertRule: baseRuleWith(ngmodels.WithForNTimes(2)), + alertRule: baseRuleWith(ngmodels.RuleMuts.WithForNTimes(2)), results: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), @@ -740,7 +736,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { }, { desc: "t1[{}:alerting] and 'for'>0 at t1", - alertRule: baseRuleWith(ngmodels.WithForNTimes(3)), + alertRule: baseRuleWith(ngmodels.RuleMuts.WithForNTimes(3)), results: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting)), @@ -809,10 +805,10 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { t.Run("no-data", func(t *testing.T) { rules := map[ngmodels.NoDataState]*ngmodels.AlertRule{ - ngmodels.NoData: baseRuleWith(ngmodels.WithNoDataExecAs(ngmodels.NoData)), - ngmodels.Alerting: baseRuleWith(ngmodels.WithNoDataExecAs(ngmodels.Alerting)), - ngmodels.OK: baseRuleWith(ngmodels.WithNoDataExecAs(ngmodels.OK)), - ngmodels.KeepLast: baseRuleWith(ngmodels.WithNoDataExecAs(ngmodels.KeepLast)), + ngmodels.NoData: baseRuleWith(ngmodels.RuleMuts.WithNoDataExecAs(ngmodels.NoData)), + ngmodels.Alerting: baseRuleWith(ngmodels.RuleMuts.WithNoDataExecAs(ngmodels.Alerting)), + ngmodels.OK: baseRuleWith(ngmodels.RuleMuts.WithNoDataExecAs(ngmodels.OK)), + ngmodels.KeepLast: baseRuleWith(ngmodels.RuleMuts.WithNoDataExecAs(ngmodels.KeepLast)), } type noDataTestCase struct { @@ -829,10 +825,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { for stateExec, rule := range rules { r := rule if len(tc.ruleMutators) > 0 { - r = ngmodels.CopyRule(r) - for _, mutateRule := range tc.ruleMutators { - mutateRule(r) - } + r = ngmodels.CopyRule(r, tc.ruleMutators...) } t.Run(fmt.Sprintf("execute as %s", stateExec), func(t *testing.T) { expectedTransitions, ok := tc.expectedTransitionsApplyNoDataErrorToAllStates[stateExec] @@ -1524,7 +1517,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { }, { desc: "t1[1:normal,2:alerting] t2[NoData] t3[NoData] and 'for'=1 at t2*,t3", - ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(1)}, + ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(1)}, results: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -1953,7 +1946,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { }, { desc: "t1[1:alerting] t2[NoData] t3[1:alerting] and 'for'=2 at t3", - ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(2)}, + ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(2)}, results: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), @@ -2678,7 +2671,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { }, { desc: "t1[{}:alerting] t2[NoData] t3[{}:alerting] and 'for'=2 at t2*,t3", - ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(2)}, + ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(2)}, results: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting)), @@ -2850,10 +2843,10 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { t.Run("error", func(t *testing.T) { rules := map[ngmodels.ExecutionErrorState]*ngmodels.AlertRule{ - ngmodels.ErrorErrState: baseRuleWith(ngmodels.WithErrorExecAs(ngmodels.ErrorErrState)), - ngmodels.AlertingErrState: baseRuleWith(ngmodels.WithErrorExecAs(ngmodels.AlertingErrState)), - ngmodels.OkErrState: baseRuleWith(ngmodels.WithErrorExecAs(ngmodels.OkErrState)), - ngmodels.KeepLastErrState: baseRuleWith(ngmodels.WithErrorExecAs(ngmodels.KeepLastErrState)), + ngmodels.ErrorErrState: baseRuleWith(ngmodels.RuleMuts.WithErrorExecAs(ngmodels.ErrorErrState)), + ngmodels.AlertingErrState: baseRuleWith(ngmodels.RuleMuts.WithErrorExecAs(ngmodels.AlertingErrState)), + ngmodels.OkErrState: baseRuleWith(ngmodels.RuleMuts.WithErrorExecAs(ngmodels.OkErrState)), + ngmodels.KeepLastErrState: baseRuleWith(ngmodels.RuleMuts.WithErrorExecAs(ngmodels.KeepLastErrState)), } cacheID := func(lbls data.Labels) string { @@ -2879,10 +2872,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { for stateExec, rule := range rules { r := rule if len(tc.ruleMutators) > 0 { - r = ngmodels.CopyRule(r) - for _, mutateRule := range tc.ruleMutators { - mutateRule(r) - } + r = ngmodels.CopyRule(r, tc.ruleMutators...) } t.Run(fmt.Sprintf("execute as %s", stateExec), func(t *testing.T) { expectedTransitions, ok := tc.expectedTransitionsApplyNoDataErrorToAllStates[stateExec] @@ -3084,7 +3074,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { }, { desc: "t1[1:alerting] t2[QueryError] and 'for'=1 at t2", - ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(1)}, + ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(1)}, results: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), @@ -3630,7 +3620,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { }, { desc: "t1[{}:alerting] t2[QueryError] and 'for'=1 at t1*,t2", - ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(1)}, + ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(1)}, results: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting)), @@ -3736,7 +3726,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) { }, { desc: "t1[{}:alerting] t2[QueryError] t3[{}:alerting] and 'for'=2 at t2,t3", - ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.WithForNTimes(2)}, + ruleMutators: []ngmodels.AlertRuleMutator{ngmodels.RuleMuts.WithForNTimes(2)}, results: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting)), diff --git a/pkg/services/ngalert/state/manager_test.go b/pkg/services/ngalert/state/manager_test.go index 38a7519f846..dbe6b697a15 100644 --- a/pkg/services/ngalert/state/manager_test.go +++ b/pkg/services/ngalert/state/manager_test.go @@ -311,7 +311,7 @@ func TestProcessEvalResults(t *testing.T) { t2 := tn(2) t3 := tn(3) - + m := models.RuleMuts baseRule := &models.AlertRule{ OrgID: 1, Title: "test_title", @@ -340,10 +340,7 @@ func TestProcessEvalResults(t *testing.T) { } baseRuleWith := func(mutators ...models.AlertRuleMutator) *models.AlertRule { - r := models.CopyRule(baseRule) - for _, mutator := range mutators { - mutator(r) - } + r := models.CopyRule(baseRule, mutators...) return r } @@ -500,7 +497,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> alerting when For is set", - alertRule: baseRuleWith(models.WithForNTimes(2)), + alertRule: baseRuleWith(m.WithForNTimes(2)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -533,7 +530,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> alerting -> noData -> alerting when For is set", - alertRule: baseRuleWith(models.WithForNTimes(2)), + alertRule: baseRuleWith(m.WithForNTimes(2)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -569,7 +566,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "pending -> alerting -> noData when For is set and NoDataState is NoData", - alertRule: baseRuleWith(models.WithForNTimes(2)), + alertRule: baseRuleWith(m.WithForNTimes(2)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), @@ -602,7 +599,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> pending when For is set but not exceeded and first result is normal", - alertRule: baseRuleWith(models.WithForNTimes(2)), + alertRule: baseRuleWith(m.WithForNTimes(2)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -630,7 +627,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> pending when For is set but not exceeded and first result is alerting", - alertRule: baseRuleWith(models.WithForNTimes(6)), + alertRule: baseRuleWith(m.WithForNTimes(6)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), @@ -657,7 +654,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> pending when For is set but not exceeded, result is NoData and NoDataState is alerting", - alertRule: baseRuleWith(models.WithForNTimes(6), models.WithNoDataExecAs(models.Alerting)), + alertRule: baseRuleWith(m.WithForNTimes(6), m.WithNoDataExecAs(models.Alerting)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -685,7 +682,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> alerting when For is exceeded, result is NoData and NoDataState is alerting", - alertRule: baseRuleWith(models.WithForNTimes(3), models.WithNoDataExecAs(models.Alerting)), + alertRule: baseRuleWith(m.WithForNTimes(3), m.WithNoDataExecAs(models.Alerting)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -877,7 +874,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> normal (NoData, KeepLastState) -> alerting -> alerting (NoData, KeepLastState) - keeps last state when result is NoData and NoDataState is KeepLast", - alertRule: baseRuleWith(models.WithForNTimes(0), models.WithNoDataExecAs(models.KeepLast)), + alertRule: baseRuleWith(m.WithForNTimes(0), m.WithNoDataExecAs(models.KeepLast)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -913,7 +910,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> pending -> pending (NoData, KeepLastState) -> alerting (NoData, KeepLastState) - keep last state respects For when result is NoData", - alertRule: baseRuleWith(models.WithForNTimes(2), models.WithNoDataExecAs(models.KeepLast)), + alertRule: baseRuleWith(m.WithForNTimes(2), m.WithNoDataExecAs(models.KeepLast)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -947,7 +944,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> normal when result is NoData and NoDataState is ok", - alertRule: baseRuleWith(models.WithNoDataExecAs(models.OK)), + alertRule: baseRuleWith(m.WithNoDataExecAs(models.OK)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -975,7 +972,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> pending when For is set but not exceeded, result is Error and ExecErrState is Alerting", - alertRule: baseRuleWith(models.WithForNTimes(6), models.WithErrorExecAs(models.AlertingErrState)), + alertRule: baseRuleWith(m.WithForNTimes(6), m.WithErrorExecAs(models.AlertingErrState)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -1004,7 +1001,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> alerting when For is exceeded, result is Error and ExecErrState is Alerting", - alertRule: baseRuleWith(models.WithForNTimes(3), models.WithErrorExecAs(models.AlertingErrState)), + alertRule: baseRuleWith(m.WithForNTimes(3), m.WithErrorExecAs(models.AlertingErrState)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -1043,7 +1040,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> error when result is Error and ExecErrState is Error", - alertRule: baseRuleWith(models.WithForNTimes(6), models.WithErrorExecAs(models.ErrorErrState)), + alertRule: baseRuleWith(m.WithForNTimes(6), m.WithErrorExecAs(models.ErrorErrState)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -1084,7 +1081,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> normal (Error, KeepLastState) -> alerting -> alerting (Error, KeepLastState) - keeps last state when result is Error and ExecErrState is KeepLast", - alertRule: baseRuleWith(models.WithForNTimes(0), models.WithErrorExecAs(models.KeepLastErrState)), + alertRule: baseRuleWith(m.WithForNTimes(0), m.WithErrorExecAs(models.KeepLastErrState)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -1120,7 +1117,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> pending -> pending (Error, KeepLastState) -> alerting (Error, KeepLastState) - keep last state respects For when result is Error", - alertRule: baseRuleWith(models.WithForNTimes(2), models.WithErrorExecAs(models.KeepLastErrState)), + alertRule: baseRuleWith(m.WithForNTimes(2), m.WithErrorExecAs(models.KeepLastErrState)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -1154,7 +1151,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> normal when result is Error and ExecErrState is OK", - alertRule: baseRuleWith(models.WithForNTimes(6), models.WithErrorExecAs(models.OkErrState)), + alertRule: baseRuleWith(m.WithForNTimes(6), m.WithErrorExecAs(models.OkErrState)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -1182,7 +1179,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "alerting -> normal when result is Error and ExecErrState is OK", - alertRule: baseRuleWith(models.WithForNTimes(6), models.WithErrorExecAs(models.OkErrState)), + alertRule: baseRuleWith(m.WithForNTimes(6), m.WithErrorExecAs(models.OkErrState)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), @@ -1210,7 +1207,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> alerting -> error when result is Error and ExecErrorState is Error", - alertRule: baseRuleWith(models.WithForNTimes(2)), + alertRule: baseRuleWith(m.WithForNTimes(2)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), @@ -1250,7 +1247,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> alerting -> error -> alerting - it should clear the error", - alertRule: baseRuleWith(models.WithForNTimes(3)), + alertRule: baseRuleWith(m.WithForNTimes(3)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -1284,7 +1281,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "normal -> alerting -> error -> no data - it should clear the error", - alertRule: baseRuleWith(models.WithForNTimes(3)), + alertRule: baseRuleWith(m.WithForNTimes(3)), evalResults: map[time.Time]eval.Results{ t1: { newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)), @@ -1319,8 +1316,8 @@ func TestProcessEvalResults(t *testing.T) { { desc: "template is correctly expanded", alertRule: baseRuleWith( - models.WithAnnotations(map[string]string{"summary": "{{$labels.pod}} is down in {{$labels.cluster}} cluster -> {{$labels.namespace}} namespace"}), - models.WithLabels(map[string]string{"label": "test", "job": "{{$labels.namespace}}/{{$labels.pod}}"}), + m.WithAnnotations(map[string]string{"summary": "{{$labels.pod}} is down in {{$labels.cluster}} cluster -> {{$labels.namespace}} namespace"}), + m.WithLabels(map[string]string{"label": "test", "job": "{{$labels.namespace}}/{{$labels.pod}}"}), ), evalResults: map[time.Time]eval.Results{ t1: { @@ -1359,7 +1356,7 @@ func TestProcessEvalResults(t *testing.T) { }, { desc: "classic condition, execution Error as Error (alerting -> query error -> alerting)", - alertRule: baseRuleWith(models.WithErrorExecAs(models.ErrorErrState)), + alertRule: baseRuleWith(m.WithErrorExecAs(models.ErrorErrState)), expectedAnnotations: 3, evalResults: map[time.Time]eval.Results{ t1: { @@ -1512,7 +1509,7 @@ func TestProcessEvalResults(t *testing.T) { } statePersister := state.NewSyncStatePersisiter(log.New("ngalert.state.manager.persist"), cfg) st := state.NewManager(cfg, statePersister) - rule := models.AlertRuleGen()() + rule := models.RuleGen.GenerateRef() var results = eval.GenerateResults(rand.Intn(4)+1, eval.ResultGen(eval.WithEvaluatedAt(clk.Now()))) states := st.ProcessEvalResults(context.Background(), clk.Now(), rule, results, make(data.Labels)) @@ -1747,7 +1744,8 @@ func TestStaleResults(t *testing.T) { } st := state.NewManager(cfg, state.NewNoopPersister()) - rule := models.AlertRuleGen(models.WithFor(0))() + gen := models.RuleGen + rule := gen.With(gen.WithFor(0)).GenerateRef() initResults := eval.Results{ eval.ResultGen(eval.WithEvaluatedAt(clk.Now()))(), diff --git a/pkg/services/ngalert/state/state_test.go b/pkg/services/ngalert/state/state_test.go index 1684b7036c1..7bb69b7d43f 100644 --- a/pkg/services/ngalert/state/state_test.go +++ b/pkg/services/ngalert/state/state_test.go @@ -706,8 +706,7 @@ func TestParseFormattedState(t *testing.T) { func TestGetRuleExtraLabels(t *testing.T) { logger := log.New() - rule := ngmodels.AlertRuleGen()() - rule.NotificationSettings = nil + rule := ngmodels.RuleGen.With(ngmodels.RuleMuts.WithNoNotificationSettings()).GenerateRef() folderTitle := uuid.NewString() ns := ngmodels.NotificationSettings{ diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index fe470991d73..4958c69b5f3 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "strings" - "sync" "testing" "time" @@ -49,10 +48,11 @@ func TestIntegrationUpdateAlertRules(t *testing.T) { FolderService: setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()), Logger: &logtest.Fake{}, } - generator := models.AlertRuleGen(withIntervalMatching(store.Cfg.BaseInterval), models.WithUniqueID()) + gen := models.RuleGen + gen = gen.With(gen.WithIntervalMatching(store.Cfg.BaseInterval)) t.Run("should increase version", func(t *testing.T) { - rule := createRule(t, store, generator) + rule := createRule(t, store, gen) newRule := models.CopyRule(rule) newRule.Title = util.GenerateShortUID() err := store.UpdateAlertRules(context.Background(), []models.UpdateRule{{ @@ -74,7 +74,7 @@ func TestIntegrationUpdateAlertRules(t *testing.T) { }) t.Run("should fail due to optimistic locking if version does not match", func(t *testing.T) { - rule := createRule(t, store, generator) + rule := createRule(t, store, gen) rule.Version-- // simulate version discrepancy newRule := models.CopyRule(rule) @@ -104,13 +104,14 @@ func TestIntegrationUpdateAlertRulesWithUniqueConstraintViolation(t *testing.T) Logger: &logtest.Fake{}, } - idMutator := models.WithUniqueID() + gen := models.RuleGen createRuleInFolder := func(title string, orgID int64, namespaceUID string) *models.AlertRule { - generator := models.AlertRuleGen(withIntervalMatching(store.Cfg.BaseInterval), idMutator, models.WithNamespace(&folder.Folder{ - UID: namespaceUID, - Title: namespaceUID, - }), withOrgID(orgID), models.WithTitle(title)) - return createRule(t, store, generator) + gen := gen.With( + gen.WithOrgID(orgID), + gen.WithIntervalMatching(store.Cfg.BaseInterval), + gen.WithNamespaceUID(namespaceUID), + ) + return createRule(t, store, gen) } t.Run("should handle update chains without unique constraint violation", func(t *testing.T) { @@ -360,9 +361,11 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) { FeatureToggles: featuremgmt.WithFeatures(), } - generator := models.AlertRuleGen(withIntervalMatching(store.Cfg.BaseInterval), models.WithUniqueID(), models.WithUniqueOrgID()) - rule1 := createRule(t, store, generator) - rule2 := createRule(t, store, generator) + gen := models.RuleGen + gen = gen.With(gen.WithIntervalMatching(store.Cfg.BaseInterval), gen.WithUniqueOrgID()) + + rule1 := createRule(t, store, gen) + rule2 := createRule(t, store, gen) parentFolderUid := uuid.NewString() parentFolderTitle := "Very Parent Folder" @@ -372,7 +375,7 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) { createFolder(t, store, rule1.NamespaceUID, rule1FolderTitle, rule1.OrgID, parentFolderUid) createFolder(t, store, rule2.NamespaceUID, rule2FolderTitle, rule2.OrgID, "") - createFolder(t, store, rule2.NamespaceUID, "same UID folder", generator().OrgID, "") // create a folder with the same UID but in the different org + createFolder(t, store, rule2.NamespaceUID, "same UID folder", gen.GenerateRef().OrgID, "") // create a folder with the same UID but in the different org tc := []struct { name string @@ -458,13 +461,6 @@ func TestIntegration_GetAlertRulesForScheduling(t *testing.T) { }) } -func withIntervalMatching(baseInterval time.Duration) func(*models.AlertRule) { - return func(rule *models.AlertRule) { - rule.IntervalSeconds = int64(baseInterval.Seconds()) * (rand.Int63n(10) + 1) - rule.For = time.Duration(rule.IntervalSeconds*rand.Int63n(9)+1) * time.Second - } -} - func TestIntegration_CountAlertRules(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") @@ -614,7 +610,12 @@ func TestIntegrationInsertAlertRules(t *testing.T) { Cfg: cfg.UnifiedAlerting, } - rules := models.GenerateAlertRules(5, models.AlertRuleGen(models.WithOrgID(orgID), withIntervalMatching(store.Cfg.BaseInterval))) + gen := models.RuleGen + rules := gen.With( + gen.WithOrgID(orgID), + gen.WithIntervalMatching(store.Cfg.BaseInterval), + ).GenerateManyRef(5) + deref := make([]models.AlertRule, 0, len(rules)) for _, rule := range rules { deref = append(deref, *rule) @@ -683,21 +684,14 @@ func TestIntegrationAlertRulesNotificationSettings(t *testing.T) { Cfg: cfg.UnifiedAlerting, } - uniqueUids := &sync.Map{} receiverName := "receiver\"-" + uuid.NewString() - rules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithOrgID(1), withIntervalMatching(store.Cfg.BaseInterval), models.WithUniqueUID(uniqueUids))) - receiveRules := models.GenerateAlertRules(3, - models.AlertRuleGen( - models.WithOrgID(1), - withIntervalMatching(store.Cfg.BaseInterval), - models.WithUniqueUID(uniqueUids), - models.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithReceiver(receiverName))))) - noise := models.GenerateAlertRules(3, - models.AlertRuleGen( - models.WithOrgID(1), - withIntervalMatching(store.Cfg.BaseInterval), - models.WithUniqueUID(uniqueUids), - models.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithMuteTimeIntervals(receiverName))))) // simulate collision of names of receiver and mute timing + + gen := models.RuleGen + gen = gen.With(gen.WithOrgID(1), gen.WithIntervalMatching(store.Cfg.BaseInterval)) + rules := gen.GenerateManyRef(3) + receiveRules := gen.With(gen.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithReceiver(receiverName)))).GenerateManyRef(3) + noise := gen.With(gen.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithMuteTimeIntervals(receiverName)))).GenerateManyRef(3) + deref := make([]models.AlertRule, 0, len(rules)+len(receiveRules)+len(noise)) for _, rule := range append(append(rules, receiveRules...), noise...) { r := *rule @@ -768,36 +762,21 @@ func TestIntegrationListNotificationSettings(t *testing.T) { Cfg: cfg.UnifiedAlerting, } - uids := &sync.Map{} - titles := &sync.Map{} receiverName := `receiver%"-👍'test` - rulesWithNotifications := models.GenerateAlertRules(5, models.AlertRuleGen( - models.WithOrgID(1), - models.WithUniqueUID(uids), - models.WithUniqueTitle(titles), - withIntervalMatching(store.Cfg.BaseInterval), - models.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithReceiver(receiverName))), - )) - rulesInOtherOrg := models.GenerateAlertRules(5, models.AlertRuleGen( - models.WithOrgID(2), - models.WithUniqueUID(uids), - models.WithUniqueTitle(titles), - withIntervalMatching(store.Cfg.BaseInterval), - models.WithNotificationSettingsGen(models.NotificationSettingsGen()), - )) - rulesWithNoNotifications := models.GenerateAlertRules(5, models.AlertRuleGen( - models.WithOrgID(1), - models.WithUniqueUID(uids), - models.WithUniqueTitle(titles), - withIntervalMatching(store.Cfg.BaseInterval), - models.WithNoNotificationSettings(), - )) - deref := make([]models.AlertRule, 0, len(rulesWithNotifications)+len(rulesWithNoNotifications)+len(rulesInOtherOrg)) - for _, rule := range append(append(rulesWithNotifications, rulesWithNoNotifications...), rulesInOtherOrg...) { - r := *rule - r.ID = 0 - deref = append(deref, r) - } + gen := models.RuleGen + gen = gen.With(gen.WithOrgID(1), gen.WithIntervalMatching(store.Cfg.BaseInterval)) + + rulesWithNotifications := gen.With( + gen.WithNotificationSettingsGen(models.NotificationSettingsGen(models.NSMuts.WithReceiver(receiverName))), + ).GenerateMany(5) + rulesInOtherOrg := gen.With( + gen.WithOrgID(2), + gen.WithNotificationSettingsGen(models.NotificationSettingsGen()), + ).GenerateMany(5) + + rulesWithNoNotifications := gen.With(gen.WithNoNotificationSettings()).GenerateMany(5) + + deref := append(append(rulesWithNotifications, rulesWithNoNotifications...), rulesInOtherOrg...) _, err := store.InsertAlertRules(context.Background(), deref) require.NoError(t, err) @@ -832,12 +811,12 @@ func TestIntegrationListNotificationSettings(t *testing.T) { // createAlertRule creates an alert rule in the database and returns it. // If a generator is not specified, uniqueness of primary key is not guaranteed. -func createRule(t *testing.T, store *DBstore, generate func() *models.AlertRule) *models.AlertRule { +func createRule(t *testing.T, store *DBstore, generator *models.AlertRuleGenerator) *models.AlertRule { t.Helper() - if generate == nil { - generate = models.AlertRuleGen(withIntervalMatching(store.Cfg.BaseInterval)) + if generator == nil { + generator = models.RuleGen.With(models.RuleMuts.WithIntervalMatching(store.Cfg.BaseInterval)) } - rule := generate() + rule := generator.GenerateRef() err := store.SQLStore.WithDbSession(context.Background(), func(sess *db.Session) error { _, err := sess.Table(models.AlertRule{}).InsertOne(rule) if err != nil { diff --git a/pkg/services/ngalert/store/deltas_test.go b/pkg/services/ngalert/store/deltas_test.go index 5a39e4cc787..2ae30ae5ef0 100644 --- a/pkg/services/ngalert/store/deltas_test.go +++ b/pkg/services/ngalert/store/deltas_test.go @@ -19,15 +19,16 @@ import ( func TestCalculateChanges(t *testing.T) { orgId := int64(rand.Int31()) + gen := models.RuleGen t.Run("detects alerts that need to be added", func(t *testing.T) { fakeStore := fakes.NewRuleStore(t) groupKey := models.GenerateGroupKey(orgId) - rules := models.GenerateAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withOrgID(orgId), simulateSubmitted, withoutUID)) + rules := gen.With(gen.WithOrgID(orgId), simulateSubmitted, withoutUID).GenerateMany(1, 5) submitted := make([]*models.AlertRuleWithOptionals, 0, len(rules)) for _, rule := range rules { - submitted = append(submitted, &models.AlertRuleWithOptionals{AlertRule: *rule}) + submitted = append(submitted, &models.AlertRuleWithOptionals{AlertRule: rule}) } changes, err := CalculateChanges(context.Background(), fakeStore, groupKey, submitted) @@ -50,8 +51,8 @@ func TestCalculateChanges(t *testing.T) { t.Run("detects alerts that need to be deleted", func(t *testing.T) { groupKey := models.GenerateGroupKey(orgId) - inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withGroupKey(groupKey))) - + inDatabase := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(1, 5) + inDatabaseMap := groupByUID(t, inDatabase) fakeStore := fakes.NewRuleStore(t) fakeStore.PutRule(context.Background(), inDatabase...) @@ -73,8 +74,11 @@ func TestCalculateChanges(t *testing.T) { t.Run("should detect alerts that needs to be updated", func(t *testing.T) { groupKey := models.GenerateGroupKey(orgId) - inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withGroupKey(groupKey))) - submittedMap, rules := models.GenerateUniqueAlertRules(len(inDatabase), models.AlertRuleGen(simulateSubmitted, withGroupKey(groupKey), withUIDs(inDatabaseMap))) + inDatabase := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(1, 5) + inDatabaseMap := groupByUID(t, inDatabase) + + rules := gen.With(simulateSubmitted, gen.WithGroupKey(groupKey), withUIDs(inDatabaseMap)).GenerateManyRef(len(inDatabase), len(inDatabase)) + submittedMap := groupByUID(t, rules) submitted := make([]*models.AlertRuleWithOptionals, 0, len(rules)) for _, rule := range rules { submitted = append(submitted, &models.AlertRuleWithOptionals{AlertRule: *rule}) @@ -104,7 +108,7 @@ func TestCalculateChanges(t *testing.T) { t.Run("should include only if there are changes ignoring specific fields", func(t *testing.T) { groupKey := models.GenerateGroupKey(orgId) - _, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(5)+1, models.AlertRuleGen(withGroupKey(groupKey))) + inDatabase := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(1, 5) submitted := make([]*models.AlertRuleWithOptionals, 0, len(inDatabase)) for _, rule := range inDatabase { @@ -132,7 +136,7 @@ func TestCalculateChanges(t *testing.T) { t.Run("should patch rule with UID specified by existing rule", func(t *testing.T) { testCases := []struct { name string - mutator func(r *models.AlertRule) + mutator models.AlertRuleMutator }{ { name: "title is empty", @@ -167,7 +171,7 @@ func TestCalculateChanges(t *testing.T) { }, } - dbRule := models.AlertRuleGen(withOrgID(orgId))() + dbRule := gen.With(gen.WithOrgID(orgId)).GenerateRef() fakeStore := fakes.NewRuleStore(t) fakeStore.PutRule(context.Background(), dbRule) @@ -176,7 +180,7 @@ func TestCalculateChanges(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - expected := models.AlertRuleGen(simulateSubmitted, testCase.mutator)() + expected := gen.With(simulateSubmitted, testCase.mutator).GenerateRef() expected.UID = dbRule.UID submitted := *expected changes, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: submitted}}) @@ -193,7 +197,8 @@ func TestCalculateChanges(t *testing.T) { t.Run("should be able to find alerts by UID in other group/namespace", func(t *testing.T) { sourceGroupKey := models.GenerateGroupKey(orgId) - inDatabaseMap, inDatabase := models.GenerateUniqueAlertRules(rand.Intn(10)+10, models.AlertRuleGen(withGroupKey(sourceGroupKey))) + inDatabase := gen.With(gen.WithGroupKey(sourceGroupKey)).GenerateManyRef(10, 20) + inDatabaseMap := groupByUID(t, inDatabase) fakeStore := fakes.NewRuleStore(t) fakeStore.PutRule(context.Background(), inDatabase...) @@ -207,7 +212,8 @@ func TestCalculateChanges(t *testing.T) { RuleGroup: groupName, } - submittedMap, rules := models.GenerateUniqueAlertRules(rand.Intn(len(inDatabase)-5)+5, models.AlertRuleGen(simulateSubmitted, withGroupKey(groupKey), withUIDs(inDatabaseMap))) + rules := gen.With(simulateSubmitted, gen.WithGroupKey(groupKey), withUIDs(inDatabaseMap)).GenerateManyRef(5, len(inDatabase)) + submittedMap := groupByUID(t, rules) submitted := make([]*models.AlertRuleWithOptionals, 0, len(rules)) for _, rule := range rules { submitted = append(submitted, &models.AlertRuleWithOptionals{AlertRule: *rule}) @@ -237,10 +243,10 @@ func TestCalculateChanges(t *testing.T) { t.Run("should fail when submitted rule has UID that does not exist in db", func(t *testing.T) { fakeStore := fakes.NewRuleStore(t) groupKey := models.GenerateGroupKey(orgId) - submitted := models.AlertRuleGen(withOrgID(orgId), simulateSubmitted)() + submitted := gen.With(gen.WithOrgID(orgId), simulateSubmitted).Generate() require.NotEqual(t, "", submitted.UID) - _, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: *submitted}}) + _, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: submitted}}) require.Error(t, err) }) @@ -256,9 +262,9 @@ func TestCalculateChanges(t *testing.T) { } groupKey := models.GenerateGroupKey(orgId) - submitted := models.AlertRuleGen(withOrgID(orgId), simulateSubmitted, withoutUID)() + submitted := gen.With(gen.WithOrgID(orgId), simulateSubmitted, withoutUID).Generate() - _, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: *submitted}}) + _, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: submitted}}) require.ErrorIs(t, err, expectedErr) }) @@ -274,19 +280,20 @@ func TestCalculateChanges(t *testing.T) { } groupKey := models.GenerateGroupKey(orgId) - submitted := models.AlertRuleGen(withOrgID(orgId), simulateSubmitted)() + submitted := gen.With(gen.WithOrgID(orgId), simulateSubmitted).Generate() - _, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: *submitted}}) + _, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: submitted}}) require.ErrorIs(t, err, expectedErr) }) } func TestCalculateAutomaticChanges(t *testing.T) { orgID := rand.Int63() + gen := models.RuleGen t.Run("should mark all rules in affected groups", func(t *testing.T) { group := models.GenerateGroupKey(orgID) - rules := models.GenerateAlertRules(10, models.AlertRuleGen(withGroupKey(group))) + rules := gen.With(gen.WithGroupKey(group)).GenerateManyRef(10) // copy rules to make sure that the function does not modify the original rules copies := make([]*models.AlertRule, 0, len(rules)) for _, rule := range rules { @@ -309,7 +316,7 @@ func TestCalculateAutomaticChanges(t *testing.T) { AffectedGroups: map[models.AlertRuleGroupKey]models.RulesGroup{ group: copies, }, - New: models.GenerateAlertRules(2, models.AlertRuleGen(withGroupKey(group))), + New: gen.With(gen.WithGroupKey(group)).GenerateManyRef(2), Update: updates, Delete: rules[5:7], } @@ -337,9 +344,9 @@ func TestCalculateAutomaticChanges(t *testing.T) { t.Run("should re-index rules in affected groups other than updated", func(t *testing.T) { group := models.GenerateGroupKey(orgID) - rules := models.GenerateAlertRules(3, models.AlertRuleGen(withGroupKey(group), models.WithSequentialGroupIndex())) + rules := gen.With(gen.WithGroupKey(group), gen.WithSequentialGroupIndex()).GenerateManyRef(3) group2 := models.GenerateGroupKey(orgID) - rules2 := models.GenerateAlertRules(4, models.AlertRuleGen(withGroupKey(group2), models.WithSequentialGroupIndex())) + rules2 := gen.With(gen.WithGroupKey(group2), gen.WithSequentialGroupIndex()).GenerateManyRef(4) movedIndex := rand.Intn(len(rules2)) movedRule := rules2[movedIndex] @@ -417,9 +424,10 @@ func TestCalculateAutomaticChanges(t *testing.T) { } func TestCalculateRuleGroupDelete(t *testing.T) { + gen := models.RuleGen fakeStore := fakes.NewRuleStore(t) groupKey := models.GenerateGroupKey(1) - otherRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithOrgID(groupKey.OrgID), models.WithNamespaceUIDNotIn(groupKey.NamespaceUID))) + otherRules := gen.With(gen.WithOrgID(groupKey.OrgID), gen.WithNamespaceUIDNotIn(groupKey.NamespaceUID)).GenerateManyRef(3) fakeStore.Rules[groupKey.OrgID] = otherRules t.Run("NotFound when group does not exist", func(t *testing.T) { @@ -429,7 +437,7 @@ func TestCalculateRuleGroupDelete(t *testing.T) { }) t.Run("set AffectedGroups when a rule refers to an existing group", func(t *testing.T) { - groupRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(groupKey))) + groupRules := gen.With(gen.WithGroupKey(groupKey)).GenerateManyRef(3) fakeStore.Rules[groupKey.OrgID] = append(fakeStore.Rules[groupKey.OrgID], groupRules...) delta, err := CalculateRuleGroupDelete(context.Background(), fakeStore, groupKey) @@ -447,9 +455,10 @@ func TestCalculateRuleGroupDelete(t *testing.T) { } func TestCalculateRuleDelete(t *testing.T) { + gen := models.RuleGen fakeStore := fakes.NewRuleStore(t) - rule := models.AlertRuleGen()() - otherRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithOrgID(rule.OrgID), models.WithNamespaceUIDNotIn(rule.NamespaceUID))) + rule := gen.GenerateRef() + otherRules := gen.With(gen.WithOrgID(rule.OrgID), gen.WithNamespaceUIDNotIn(rule.NamespaceUID)).GenerateManyRef(3) fakeStore.Rules[rule.OrgID] = otherRules t.Run("nil when a rule does not exist", func(t *testing.T) { @@ -459,7 +468,7 @@ func TestCalculateRuleDelete(t *testing.T) { }) t.Run("set AffectedGroups when a rule refers to an existing group", func(t *testing.T) { - groupRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(rule.GetGroupKey()))) + groupRules := gen.With(gen.WithGroupKey(rule.GetGroupKey())).GenerateManyRef(3) groupRules = append(groupRules, rule) fakeStore.Rules[rule.OrgID] = append(fakeStore.Rules[rule.OrgID], groupRules...) @@ -479,10 +488,11 @@ func TestCalculateRuleDelete(t *testing.T) { } func TestCalculateRuleUpdate(t *testing.T) { + gen := models.RuleGen fakeStore := fakes.NewRuleStore(t) - rule := models.AlertRuleGen()() - otherRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithOrgID(rule.OrgID), models.WithNamespaceUIDNotIn(rule.NamespaceUID))) - groupRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(rule.GetGroupKey()))) + rule := gen.GenerateRef() + otherRules := gen.With(gen.WithOrgID(rule.OrgID), gen.WithNamespaceUIDNotIn(rule.NamespaceUID)).GenerateManyRef(3) + groupRules := gen.With(gen.WithGroupKey(rule.GetGroupKey())).GenerateManyRef(3) groupRules = append(groupRules, rule) fakeStore.Rules[rule.OrgID] = append(otherRules, groupRules...) @@ -520,7 +530,7 @@ func TestCalculateRuleUpdate(t *testing.T) { t.Run("when a rule is moved between groups", func(t *testing.T) { sourceGroupKey := rule.GetGroupKey() targetGroupKey := models.GenerateGroupKey(rule.OrgID) - targetGroup := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(targetGroupKey))) + targetGroup := gen.With(gen.WithGroupKey(targetGroupKey)).GenerateManyRef(3) fakeStore.Rules[rule.OrgID] = append(fakeStore.Rules[rule.OrgID], targetGroup...) cp := models.CopyRule(rule) @@ -548,9 +558,10 @@ func TestCalculateRuleUpdate(t *testing.T) { } func TestCalculateRuleCreate(t *testing.T) { + gen := models.RuleGen t.Run("when a rule refers to a new group", func(t *testing.T) { fakeStore := fakes.NewRuleStore(t) - rule := models.AlertRuleGen()() + rule := gen.GenerateRef() delta, err := CalculateRuleCreate(context.Background(), fakeStore, rule) require.NoError(t, err) @@ -565,10 +576,10 @@ func TestCalculateRuleCreate(t *testing.T) { t.Run("when a rule refers to an existing group", func(t *testing.T) { fakeStore := fakes.NewRuleStore(t) - rule := models.AlertRuleGen()() + rule := gen.GenerateRef() - groupRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithGroupKey(rule.GetGroupKey()))) - otherRules := models.GenerateAlertRules(3, models.AlertRuleGen(models.WithOrgID(rule.OrgID), models.WithNamespaceUIDNotIn(rule.NamespaceUID))) + groupRules := gen.With(gen.WithGroupKey(rule.GetGroupKey())).GenerateManyRef(3) + otherRules := gen.With(gen.WithGroupKey(rule.GetGroupKey()), gen.WithNamespaceUIDNotIn(rule.NamespaceUID)).GenerateManyRef(3) fakeStore.Rules[rule.OrgID] = append(groupRules, otherRules...) delta, err := CalculateRuleCreate(context.Background(), fakeStore, rule) @@ -591,25 +602,11 @@ func simulateSubmitted(rule *models.AlertRule) { rule.Updated = time.Time{} } -func withOrgID(orgId int64) func(rule *models.AlertRule) { - return func(rule *models.AlertRule) { - rule.OrgID = orgId - } -} - func withoutUID(rule *models.AlertRule) { rule.UID = "" } -func withGroupKey(groupKey models.AlertRuleGroupKey) func(rule *models.AlertRule) { - return func(rule *models.AlertRule) { - rule.RuleGroup = groupKey.RuleGroup - rule.OrgID = groupKey.OrgID - rule.NamespaceUID = groupKey.NamespaceUID - } -} - -func withUIDs(uids map[string]*models.AlertRule) func(rule *models.AlertRule) { +func withUIDs(uids map[string]*models.AlertRule) models.AlertRuleMutator { unused := make([]string, 0, len(uids)) for s := range uids { unused = append(unused, s) @@ -635,3 +632,14 @@ func randFolder() *folder.Folder { CreatedBy: 0, } } + +func groupByUID(t *testing.T, list []*models.AlertRule) map[string]*models.AlertRule { + result := make(map[string]*models.AlertRule, len(list)) + for _, rule := range list { + if _, ok := result[rule.UID]; ok { + t.Fatalf("expected unique UID for rule %s but duplicate", rule.UID) + } + result[rule.UID] = rule + } + return result +} From 0f4db3f5ad2316ec1dbb3672535abbeb2fe150bb Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Tue, 30 Apr 2024 07:58:25 +0200 Subject: [PATCH 200/222] Fix: yarn build in DockerFile (#86858) --- Dockerfile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7a68afa3b00..04991627ceb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,18 +14,18 @@ ENV NODE_OPTIONS=--max_old_space_size=8000 WORKDIR /tmp/grafana -COPY package.json yarn.lock .yarnrc.yml ./ +COPY package.json project.json nx.json yarn.lock .yarnrc.yml ./ COPY .yarn .yarn COPY packages packages COPY plugins-bundled plugins-bundled COPY public public +COPY LICENSE ./ RUN apk add --no-cache make build-base python3 RUN yarn install --immutable COPY tsconfig.json .eslintrc .editorconfig .browserslistrc .prettierrc.js ./ -COPY public public COPY scripts scripts COPY emails emails @@ -77,7 +77,6 @@ COPY pkg pkg COPY scripts scripts COPY conf conf COPY .github .github -COPY LICENSE ./ ENV COMMIT_SHA=${COMMIT_SHA} ENV BUILD_BRANCH=${BUILD_BRANCH} @@ -179,7 +178,7 @@ RUN if [ ! $(getent group "$GF_GID") ]; then \ COPY --from=go-src /tmp/grafana/bin/grafana* /tmp/grafana/bin/*/grafana* ./bin/ COPY --from=js-src /tmp/grafana/public ./public -COPY --from=go-src /tmp/grafana/LICENSE ./ +COPY --from=js-src /tmp/grafana/LICENSE ./ EXPOSE 3000 From 1cb3f332a17923f58cfa75cdb9d5eaab7ed66f45 Mon Sep 17 00:00:00 2001 From: Misi Date: Tue, 30 Apr 2024 08:54:20 +0200 Subject: [PATCH 201/222] Chore: Remove extra sql select from the Insert function of userimpl.store (#87060) Remove getAnyUserType --- pkg/services/user/userimpl/store.go | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/pkg/services/user/userimpl/store.go b/pkg/services/user/userimpl/store.go index 597b649d3aa..d4c8d2513a1 100644 --- a/pkg/services/user/userimpl/store.go +++ b/pkg/services/user/userimpl/store.go @@ -75,11 +75,6 @@ func (ss *sqlStore) Insert(ctx context.Context, cmd *user.User) (int64, error) { return 0, err } - // verify that user was created and cmd.ID was updated with the actual new userID - _, err = ss.getAnyUserType(ctx, cmd.ID) - if err != nil { - return 0, err - } return cmd.ID, nil } @@ -588,22 +583,6 @@ func (ss *sqlStore) Search(ctx context.Context, query *user.SearchUsersQuery) (* return &result, err } -// getAnyUserType searches for a user record by ID. The user account may be a service account. -func (ss *sqlStore) getAnyUserType(ctx context.Context, userID int64) (*user.User, error) { - usr := user.User{ID: userID} - err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { - has, err := sess.Get(&usr) - if err != nil { - return err - } - if !has { - return user.ErrUserNotFound - } - return nil - }) - return &usr, err -} - func setOptional[T any](v *T, add func(v T)) { if v != nil { add(*v) From 78cda7ff5c7f2965c4ca2050fa047bbaaa5b5b00 Mon Sep 17 00:00:00 2001 From: Andreas 'count' Kotes Date: Tue, 30 Apr 2024 09:21:49 +0200 Subject: [PATCH 202/222] Schema: add missing insertNulls to GraphFieldConfig (#85861) add missing insertNulls to GraphFieldConfig Co-authored-by: joshhunt --- packages/grafana-schema/src/common/common.gen.ts | 1 + packages/grafana-schema/src/common/mudball.cue | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/grafana-schema/src/common/common.gen.ts b/packages/grafana-schema/src/common/common.gen.ts index a0c69ebbb06..6fca2bf56fd 100644 --- a/packages/grafana-schema/src/common/common.gen.ts +++ b/packages/grafana-schema/src/common/common.gen.ts @@ -601,6 +601,7 @@ export enum SortOrder { export interface GraphFieldConfig extends LineConfig, FillConfig, PointsConfig, AxisConfig, BarConfig, StackableFieldConfig, HideableFieldConfig { drawStyle?: GraphDrawStyle; gradientMode?: GraphGradientMode; + insertNulls?: (boolean | number); thresholdsStyle?: GraphThresholdsStyleConfig; transform?: GraphTransform; } diff --git a/packages/grafana-schema/src/common/mudball.cue b/packages/grafana-schema/src/common/mudball.cue index d08fbeff153..a8fe6f5c291 100644 --- a/packages/grafana-schema/src/common/mudball.cue +++ b/packages/grafana-schema/src/common/mudball.cue @@ -224,6 +224,7 @@ GraphFieldConfig: { gradientMode?: GraphGradientMode thresholdsStyle?: GraphThresholdsStyleConfig transform?: GraphTransform + insertNulls?: bool | number } @cuetsy(kind="interface") // TODO docs From 9369f07e32298ad5868858e92dc288ed101a9863 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Tue, 30 Apr 2024 10:34:52 +0200 Subject: [PATCH 203/222] Alerting: Immutable plugin rules and alerting plugins extensions (#86042) * Add pluginsApi * Add rule origin badge * Make plugin provided rules read-only * Add plugin settings caching, add plugin icon on the rule detail page * Add basic extension point for custom plugin actions * Add support for alerting and recording rule extensions * Move plugin hooks to their own files * Add plugin custom actions to the alert list more actions menu * Add custom actions renderign test * Add more tests * Cleanup * Use test-utils in RuleViewer tests * Remove __grafana_origin label from the label autocomplete * Remove pluginsApi * Add plugin badge tooltip * Update tests * Add grafana origin constant key, remove unused code * Hide the grafana origin label * Fix typo, rename alerting extension points * Unify private labels handling * Add reactive plugins registry handling * Update tests * Fix tests * Fix tests * Fix panel tests * Add getRuleOrigin tests * Tests refactor, smalle improvements * Rename rule origin to better reflect the intent --------- Co-authored-by: Tom Ratcliffe --- .../src/types/pluginExtensions.ts | 2 + .../unified/components/AlertLabels.tsx | 6 +- .../components/alert-groups/GroupBy.tsx | 4 +- .../rule-editor/labels/LabelsField.tsx | 14 +- .../components/rule-viewer/Actions.tsx | 16 ++ .../rule-viewer/RuleViewer.test.tsx | 138 +++++++++++++----- .../components/rule-viewer/RuleViewer.tsx | 9 +- .../rule-viewer/__mocks__/server.ts | 50 +++---- .../components/rules/RuleActionsButtons.tsx | 21 ++- .../rules/RuleListGroupView.test.tsx | 7 +- .../components/rules/RulesTable.test.tsx | 6 + .../unified/components/rules/RulesTable.tsx | 12 +- .../alerting/unified/hooks/useAbilities.ts | 12 +- public/app/features/alerting/unified/mocks.ts | 14 ++ .../alerting/unified/mocks/plugins.ts | 17 +-- .../unified/plugins/PluginOriginBadge.tsx | 32 ++++ .../plugins/useRulePluginLinkExtensions.ts | 92 ++++++++++++ .../alerting/unified/testSetup/plugins.ts | 97 ++++++++++++ .../features/alerting/unified/utils/labels.ts | 8 + .../alerting/unified/utils/matchers.ts | 6 +- .../alerting/unified/utils/rules.test.ts | 45 ++++++ .../features/alerting/unified/utils/rules.ts | 38 +++++ .../PanelDataAlertingTab.test.tsx | 7 +- .../panel/alertlist/GroupByWithLoading.tsx | 5 +- public/app/plugins/panel/alertlist/util.ts | 4 - 25 files changed, 552 insertions(+), 110 deletions(-) create mode 100644 public/app/features/alerting/unified/plugins/PluginOriginBadge.tsx create mode 100644 public/app/features/alerting/unified/plugins/useRulePluginLinkExtensions.ts create mode 100644 public/app/features/alerting/unified/testSetup/plugins.ts create mode 100644 public/app/features/alerting/unified/utils/rules.test.ts diff --git a/packages/grafana-data/src/types/pluginExtensions.ts b/packages/grafana-data/src/types/pluginExtensions.ts index 85a123754f8..c9ed2b0506d 100644 --- a/packages/grafana-data/src/types/pluginExtensions.ts +++ b/packages/grafana-data/src/types/pluginExtensions.ts @@ -117,6 +117,8 @@ export type PluginExtensionEventHelpers = { export enum PluginExtensionPoints { AlertInstanceAction = 'grafana/alerting/instance/action', AlertingHomePage = 'grafana/alerting/home', + AlertingAlertingRuleAction = 'grafana/alerting/alertingrule/action', + AlertingRecordingRuleAction = 'grafana/alerting/recordingrule/action', CommandPalette = 'grafana/commandpalette/action', DashboardPanelMenu = 'grafana/dashboard/panel/menu', DataSourceConfig = 'grafana/datasources/config', diff --git a/public/app/features/alerting/unified/components/AlertLabels.tsx b/public/app/features/alerting/unified/components/AlertLabels.tsx index 30bfeb817b7..98a9102466b 100644 --- a/public/app/features/alerting/unified/components/AlertLabels.tsx +++ b/public/app/features/alerting/unified/components/AlertLabels.tsx @@ -6,6 +6,8 @@ import React, { useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { Button, getTagColorsFromName, useStyles2 } from '@grafana/ui'; +import { isPrivateLabel } from '../utils/labels'; + import { Label, LabelSize } from './Label'; interface Props { @@ -20,7 +22,7 @@ export const AlertLabels = ({ labels, commonLabels = {}, size }: Props) => { const labelsToShow = chain(labels) .toPairs() - .reject(isPrivateKey) + .reject(isPrivateLabel) .reject(([key]) => (showCommonLabels ? false : key in commonLabels)) .value(); @@ -63,8 +65,6 @@ function getLabelColor(input: string): string { return getTagColorsFromName(input).color; } -const isPrivateKey = ([key, _]: [string, string]) => key.startsWith('__') && key.endsWith('__'); - const getStyles = (theme: GrafanaTheme2, size?: LabelSize) => ({ wrapper: css` display: flex; diff --git a/public/app/features/alerting/unified/components/alert-groups/GroupBy.tsx b/public/app/features/alerting/unified/components/alert-groups/GroupBy.tsx index a35dba726d1..5cd7443478e 100644 --- a/public/app/features/alerting/unified/components/alert-groups/GroupBy.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/GroupBy.tsx @@ -5,6 +5,8 @@ import { SelectableValue } from '@grafana/data'; import { Icon, Label, MultiSelect } from '@grafana/ui'; import { AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types'; +import { isPrivateLabelKey } from '../../utils/labels'; + interface Props { groups: AlertmanagerGroup[]; groupBy: string[]; @@ -13,7 +15,7 @@ interface Props { export const GroupBy = ({ groups, groupBy, onGroupingChange }: Props) => { const labelKeyOptions = uniq(groups.flatMap((group) => group.alerts).flatMap(({ labels }) => Object.keys(labels))) - .filter((label) => !(label.startsWith('__') && label.endsWith('__'))) // Filter out private labels + .filter((label) => !isPrivateLabelKey(label)) // Filter out private labels .map((key) => ({ label: key, value: key, diff --git a/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.tsx b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.tsx index 685b1626c67..3573f37ef6c 100644 --- a/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.tsx @@ -12,6 +12,7 @@ import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSel import { fetchRulerRulesIfNotFetchedYet } from '../../../state/actions'; import { SupportedPlugin } from '../../../types/pluginBridges'; import { RuleFormValues } from '../../../types/rule-form'; +import { isPrivateLabelKey } from '../../../utils/labels'; import AlertLabelDropdown from '../../AlertLabelDropdown'; import { AlertLabels } from '../../AlertLabels'; import { NeedHelpInfo } from '../NeedHelpInfo'; @@ -146,6 +147,9 @@ export function LabelsSubForm({ dataSourceName, onClose, initialLabels }: Labels ); } + +const isKeyAllowed = (labelKey: string) => !isPrivateLabelKey(labelKey); + export function useCombinedLabels( dataSourceName: string, labelsPluginInstalled: boolean, @@ -169,12 +173,12 @@ export function useCombinedLabels( //------- Convert the keys from the ops labels to options for the dropdown const keysFromGopsLabels = useMemo(() => { - return mapLabelsToOptions(Object.keys(labelsByKeyOps), labelsInSubform); + return mapLabelsToOptions(Object.keys(labelsByKeyOps).filter(isKeyAllowed), labelsInSubform); }, [labelsByKeyOps, labelsInSubform]); //------- Convert the keys from the existing alerts to options for the dropdown const keysFromExistingAlerts = useMemo(() => { - return mapLabelsToOptions(Object.keys(labelsByKeyFromExisingAlerts), labelsInSubform); + return mapLabelsToOptions(Object.keys(labelsByKeyFromExisingAlerts).filter(isKeyAllowed), labelsInSubform); }, [labelsByKeyFromExisingAlerts, labelsInSubform]); // create two groups of labels, one for ops and one for custom @@ -238,6 +242,10 @@ export function useCombinedLabels( const getValuesForLabel = useCallback( (key: string) => { + if (!isKeyAllowed(key)) { + return []; + } + // values from existing alerts will take precedence over values from ops if (selectedKeyIsFromAlerts || !labelsPluginInstalled) { return mapLabelsToOptions(labelsByKeyFromExisingAlerts[key]); @@ -254,6 +262,7 @@ export function useCombinedLabels( getValuesForLabel, }; } + /* We will suggest labels from two sources: existing alerts and ops labels. We only will suggest labels from ops if the grafana-labels-app plugin is installed @@ -262,6 +271,7 @@ export function useCombinedLabels( export interface LabelsWithSuggestionsProps { dataSourceName: string; } + export function LabelsWithSuggestions({ dataSourceName }: LabelsWithSuggestionsProps) { const styles = useStyles2(getStyles); const { diff --git a/public/app/features/alerting/unified/components/rule-viewer/Actions.tsx b/public/app/features/alerting/unified/components/rule-viewer/Actions.tsx index ee57acb14b2..ff9d296d417 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/Actions.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/Actions.tsx @@ -7,6 +7,7 @@ import MenuItemPauseRule from 'app/features/alerting/unified/components/MenuItem import { CombinedRule, RuleIdentifier } from 'app/types/unified-alerting'; import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities'; +import { useRulePluginLinkExtension } from '../../plugins/useRulePluginLinkExtensions'; import { createShareLink, isLocalDevEnv, isOpenSourceEdition, makeRuleBasedSilenceLink } from '../../utils/misc'; import * as ruleId from '../../utils/rule-id'; import { createUrl } from '../../utils/url'; @@ -22,6 +23,7 @@ interface Props { export const useAlertRulePageActions = ({ handleDelete, handleDuplicateRule }: Props) => { const { rule, identifier } = useAlertRule(); + const rulePluginLinkExtension = useRulePluginLinkExtension(rule); // check all abilities and permissions const [editSupported, editAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Update); @@ -71,6 +73,20 @@ export const useAlertRulePageActions = ({ handleDelete, handleDuplicateRule }: P childItems={[]} /> )} + {rulePluginLinkExtension.length > 0 && ( + <> + + {rulePluginLinkExtension.map((extension) => ( + + ))} + + )} {canDelete && ( <> diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx index 1a82e056194..65d5453fee0 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx @@ -1,16 +1,23 @@ -import { render, waitFor, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; import React from 'react'; -import { TestProvider } from 'test/helpers/TestProvider'; +import { render, waitFor, screen, userEvent } from 'test/test-utils'; import { byText, byRole } from 'testing-library-selector'; -import { setBackendSrv } from '@grafana/runtime'; +import { setBackendSrv, setPluginExtensionsHook } from '@grafana/runtime'; import { backendSrv } from 'app/core/services/backend_srv'; import { AccessControlAction } from 'app/types'; import { CombinedRule, RuleIdentifier } from 'app/types/unified-alerting'; -import { getCloudRule, getGrafanaRule, grantUserPermissions } from '../../mocks'; +import { + getCloudRule, + getGrafanaRule, + grantUserPermissions, + mockDataSource, + mockPluginLinkExtension, +} from '../../mocks'; +import { setupDataSources } from '../../testSetup/datasources'; +import { plugins, setupPlugins } from '../../testSetup/plugins'; import { Annotation } from '../../utils/constants'; +import { DataSourceType } from '../../utils/datasource'; import * as ruleId from '../../utils/rule-id'; import { AlertRuleProvider } from './RuleContext'; @@ -33,20 +40,58 @@ const ELEMENTS = { button: byRole('button', { name: /More/i }), actions: { silence: byRole('link', { name: /Silence/i }), - declareIncident: byRole('menuitem', { name: /Declare incident/i }), duplicate: byRole('menuitem', { name: /Duplicate/i }), copyLink: byRole('menuitem', { name: /Copy link/i }), export: byRole('menuitem', { name: /Export/i }), delete: byRole('menuitem', { name: /Delete/i }), }, + pluginActions: { + sloDashboard: byRole('link', { name: /SLO dashboard/i }), + declareIncident: byRole('link', { name: /Declare incident/i }), + assertsWorkbench: byRole('link', { name: /Open workbench/i }), + }, }, }, }; +const { apiHandlers: pluginApiHandlers } = setupPlugins(plugins.slo, plugins.incident, plugins.asserts); + +const server = createMockGrafanaServer(...pluginApiHandlers); + +setupDataSources(mockDataSource({ type: DataSourceType.Prometheus, name: 'mimir-1' })); +setPluginExtensionsHook(() => ({ + extensions: [ + mockPluginLinkExtension({ pluginId: 'grafana-slo-app', title: 'SLO dashboard', path: '/a/grafana-slo-app' }), + mockPluginLinkExtension({ + pluginId: 'grafana-asserts-app', + title: 'Open workbench', + path: '/a/grafana-asserts-app', + }), + ], + isLoading: false, +})); + +beforeAll(() => { + grantUserPermissions([ + AccessControlAction.AlertingRuleCreate, + AccessControlAction.AlertingRuleRead, + AccessControlAction.AlertingRuleUpdate, + AccessControlAction.AlertingRuleDelete, + AccessControlAction.AlertingInstanceCreate, + ]); + setBackendSrv(backendSrv); +}); + +beforeEach(() => { + server.listen(); +}); + +afterAll(() => { + server.close(); +}); + describe('RuleViewer', () => { describe('Grafana managed alert rule', () => { - const server = createMockGrafanaServer(); - const mockRule = getGrafanaRule( { name: 'Test alert', @@ -71,29 +116,6 @@ describe('RuleViewer', () => { ); const mockRuleIdentifier = ruleId.fromCombinedRule('grafana', mockRule); - beforeAll(() => { - grantUserPermissions([ - AccessControlAction.AlertingRuleCreate, - AccessControlAction.AlertingRuleRead, - AccessControlAction.AlertingRuleUpdate, - AccessControlAction.AlertingRuleDelete, - AccessControlAction.AlertingInstanceCreate, - ]); - setBackendSrv(backendSrv); - }); - - beforeEach(() => { - server.listen(); - }); - - afterAll(() => { - server.close(); - }); - - afterEach(() => { - server.resetHandlers(); - }); - it('should render a Grafana managed alert rule', async () => { await renderRuleViewer(mockRule, mockRuleIdentifier); @@ -131,8 +153,12 @@ describe('RuleViewer', () => { }); }); - describe.skip('Data source managed alert rule', () => { - const mockRule = getCloudRule({ name: 'cloud test alert' }); + describe('Data source managed alert rule', () => { + const mockRule = getCloudRule({ + name: 'cloud test alert', + annotations: { [Annotation.summary]: 'cloud summary', [Annotation.runbookURL]: 'https://runbook.example.com' }, + group: { name: 'Cloud group', interval: '15m', rules: [], totals: { alerting: 1 } }, + }); const mockRuleIdentifier = ruleId.fromCombinedRule('mimir-1', mockRule); beforeAll(() => { @@ -146,14 +172,53 @@ describe('RuleViewer', () => { renderRuleViewer(mockRule, mockRuleIdentifier); // assert on basic info to be vissible - expect(screen.getByText('Test alert')).toBeInTheDocument(); + expect(screen.getByText('cloud test alert')).toBeInTheDocument(); expect(screen.getByText('Firing')).toBeInTheDocument(); expect(screen.getByText(mockRule.annotations[Annotation.summary])).toBeInTheDocument(); - expect(screen.getByRole('link', { name: 'View panel' })).toBeInTheDocument(); expect(screen.getByRole('link', { name: mockRule.annotations[Annotation.runbookURL] })).toBeInTheDocument(); expect(screen.getByText(`Every ${mockRule.group.interval}`)).toBeInTheDocument(); }); + + it('should render custom plugin actions for a plugin-provided rule', async () => { + const sloRule = getCloudRule({ + name: 'slo test alert', + labels: { __grafana_origin: 'plugin/grafana-slo-app' }, + }); + const sloRuleIdentifier = ruleId.fromCombinedRule('mimir-1', sloRule); + + const user = userEvent.setup(); + + renderRuleViewer(sloRule, sloRuleIdentifier); + + expect(ELEMENTS.actions.more.button.get()).toBeInTheDocument(); + + await user.click(ELEMENTS.actions.more.button.get()); + + expect(ELEMENTS.actions.more.pluginActions.sloDashboard.get()).toBeInTheDocument(); + expect(ELEMENTS.actions.more.pluginActions.assertsWorkbench.query()).not.toBeInTheDocument(); + + await waitFor(() => expect(ELEMENTS.actions.more.pluginActions.declareIncident.get()).toBeEnabled()); + }); + + it('should render different custom plugin actions for a different plugin-provided rule', async () => { + const assertsRule = getCloudRule({ + name: 'asserts test alert', + labels: { __grafana_origin: 'plugin/grafana-asserts-app' }, + }); + const assertsRuleIdentifier = ruleId.fromCombinedRule('mimir-1', assertsRule); + + renderRuleViewer(assertsRule, assertsRuleIdentifier); + + expect(ELEMENTS.actions.more.button.get()).toBeInTheDocument(); + + await userEvent.click(ELEMENTS.actions.more.button.get()); + + expect(ELEMENTS.actions.more.pluginActions.assertsWorkbench.get()).toBeInTheDocument(); + expect(ELEMENTS.actions.more.pluginActions.sloDashboard.query()).not.toBeInTheDocument(); + + await waitFor(() => expect(ELEMENTS.actions.more.pluginActions.declareIncident.get()).toBeEnabled()); + }); }); }); @@ -161,8 +226,7 @@ const renderRuleViewer = async (rule: CombinedRule, identifier: RuleIdentifier) render( - , - { wrapper: TestProvider } + ); await waitFor(() => expect(ELEMENTS.loading.query()).not.toBeInTheDocument()); diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx index d6899597755..ed317f4b987 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx @@ -11,9 +11,12 @@ import { CombinedRule, RuleHealth, RuleIdentifier } from 'app/types/unified-aler import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto'; import { defaultPageNav } from '../../RuleViewer'; +import { PluginOriginBadge } from '../../plugins/PluginOriginBadge'; import { Annotation } from '../../utils/constants'; import { makeDashboardLink, makePanelLink } from '../../utils/misc'; import { + RulePluginOrigin, + getRulePluginOrigin, isAlertingRule, isFederatedRuleGroup, isGrafanaRulerRule, @@ -73,6 +76,7 @@ const RuleViewer = () => { const isPaused = isGrafanaRulerRule(rule.rulerRule) && isGrafanaRulerRulePaused(rule.rulerRule); const showError = hasError && !isPaused; + const ruleOrigin = getRulePluginOrigin(rule); const summary = annotations[Annotation.summary]; @@ -88,6 +92,7 @@ const RuleViewer = () => { state={isAlertType ? promRule.state : undefined} health={rule.promRule?.health} ruleType={rule.promRule?.type} + ruleOrigin={ruleOrigin} /> )} actions={actions} @@ -223,15 +228,17 @@ interface TitleProps { state?: PromAlertingRuleState; health?: RuleHealth; ruleType?: PromRuleType; + ruleOrigin?: RulePluginOrigin; } -export const Title = ({ name, paused = false, state, health, ruleType }: TitleProps) => { +export const Title = ({ name, paused = false, state, health, ruleType, ruleOrigin }: TitleProps) => { const styles = useStyles2(getStyles); const isRecordingRule = ruleType === PromRuleType.Recording; return (
+ {ruleOrigin && } {name} diff --git a/public/app/features/alerting/unified/components/rule-viewer/__mocks__/server.ts b/public/app/features/alerting/unified/components/rule-viewer/__mocks__/server.ts index de4dcdcf261..4bfdda20e06 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/__mocks__/server.ts +++ b/public/app/features/alerting/unified/components/rule-viewer/__mocks__/server.ts @@ -1,11 +1,12 @@ -import { http, HttpResponse } from 'msw'; -import { SetupServer, setupServer } from 'msw/node'; +import { http, HttpResponse, RequestHandler } from 'msw'; +import { setupServer } from 'msw/node'; import { AlertmanagersChoiceResponse } from 'app/features/alerting/unified/api/alertmanagerApi'; -import { mockAlertmanagerChoiceResponse } from 'app/features/alerting/unified/mocks/alertmanagerApi'; import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; +import { alertmanagerChoiceHandler } from '../../../mocks/alertmanagerApi'; + const alertmanagerChoiceMockedResponse: AlertmanagersChoiceResponse = { alertmanagersChoice: AlertmanagerChoice.Internal, numExternalAlertmanagers: 0, @@ -18,39 +19,24 @@ const folderAccess = { [AccessControlAction.AlertingRuleDelete]: true, }; -export function createMockGrafanaServer() { - const server = setupServer(); +export function createMockGrafanaServer(...handlers: RequestHandler[]) { + const folderHandler = mockFolderAccess(folderAccess); + const amChoiceHandler = alertmanagerChoiceHandler(alertmanagerChoiceMockedResponse); - mockFolderAccess(server, folderAccess); - mockAlertmanagerChoiceResponse(server, alertmanagerChoiceMockedResponse); - mockGrafanaIncidentPluginSettings(server); - - return server; + return setupServer(folderHandler, amChoiceHandler, ...handlers); } // this endpoint is used to determine of we have edit / delete permissions for the Grafana managed alert rule // a user must alsso have permissions for the folder (namespace) in which the alert rule is stored -function mockFolderAccess(server: SetupServer, accessControl: Partial>) { - server.use( - http.get('/api/folders/:uid', ({ request }) => { - const url = new URL(request.url); - const uid = url.searchParams.get('uid'); +function mockFolderAccess(accessControl: Partial>) { + return http.get('/api/folders/:uid', ({ request }) => { + const url = new URL(request.url); + const uid = url.searchParams.get('uid'); - return HttpResponse.json({ - title: 'My Folder', - uid, - accessControl, - }); - }) - ); - - return server; -} - -function mockGrafanaIncidentPluginSettings(server: SetupServer) { - server.use( - http.get('/api/plugins/grafana-incident-app/settings', () => { - return HttpResponse.json({}); - }) - ); + return HttpResponse.json({ + title: 'My Folder', + uid, + accessControl, + }); + }); } diff --git a/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx index cf66b4b76b7..484910ad4bb 100644 --- a/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx @@ -24,6 +24,7 @@ import { useDispatch } from 'app/types'; import { CombinedRule, RuleIdentifier, RulesSource } from 'app/types/unified-alerting'; import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities'; +import { useRulePluginLinkExtension } from '../../plugins/useRulePluginLinkExtensions'; import { deleteRuleAction, fetchAllPromAndRulerRulesAction } from '../../state/actions'; import { getRulesSourceName } from '../../utils/datasource'; import { createShareLink, createViewLink } from '../../utils/misc'; @@ -59,6 +60,8 @@ export const RuleActionsButtons = ({ rule, rulesSource }: Props) => { const isProvisioned = isGrafanaRulerRule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); + const ruleExtensionLinks = useRulePluginLinkExtension(rule); + const [editRuleSupported, editRuleAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Update); const [deleteRuleSupported, deleteRuleAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Delete); const [duplicateRuleSupported, duplicateRuleAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Duplicate); @@ -178,10 +181,22 @@ export const RuleActionsButtons = ({ rule, rulesSource }: Props) => { /> ); } + } - if (canDeleteRule) { - moreActions.push( setRuleToDelete(rule)} />); - } + if (ruleExtensionLinks.length > 0) { + moreActions.push( + , + ...ruleExtensionLinks.map((extension) => ( + + )) + ); + } + + if (rulerRule && canDeleteRule) { + moreActions.push( + , + setRuleToDelete(rule)} /> + ); } if (buttons.length || moreActions.length) { diff --git a/public/app/features/alerting/unified/components/rules/RuleListGroupView.test.tsx b/public/app/features/alerting/unified/components/rules/RuleListGroupView.test.tsx index 5e35dbdda97..0c51d4ccbb9 100644 --- a/public/app/features/alerting/unified/components/rules/RuleListGroupView.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleListGroupView.test.tsx @@ -4,7 +4,7 @@ import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; import { byRole } from 'testing-library-selector'; -import { locationService } from '@grafana/runtime'; +import { locationService, setPluginExtensionsHook } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; import { configureStore } from 'app/store/configureStore'; import { AccessControlAction } from 'app/types'; @@ -23,6 +23,11 @@ const ui = { cloudRulesHeading: byRole('heading', { name: 'Mimir / Cortex / Loki' }), }; +setPluginExtensionsHook(() => ({ + extensions: [], + isLoading: false, +})); + describe('RuleListGroupView', () => { describe('RBAC', () => { it('Should display Grafana rules when the user has the alert rule read permission', async () => { diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx index 3065719d8c0..7f29dcc73ac 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx @@ -5,6 +5,7 @@ import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import { byRole } from 'testing-library-selector'; +import { setPluginExtensionsHook } from '@grafana/runtime'; import { configureStore } from 'app/store/configureStore'; import { CombinedRule } from 'app/types/unified-alerting'; @@ -19,6 +20,11 @@ const mocks = { useAlertRuleAbility: jest.mocked(useAlertRuleAbility), }; +setPluginExtensionsHook(() => ({ + extensions: [], + isLoading: false, +})); + const ui = { actionButtons: { edit: byRole('link', { name: 'Edit' }), diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.tsx index 56985d845ef..e1ae729a4f8 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.tsx @@ -16,8 +16,9 @@ import { CombinedRule } from 'app/types/unified-alerting'; import { DEFAULT_PER_PAGE_PAGINATION } from '../../../../../core/constants'; import { useHasRuler } from '../../hooks/useHasRuler'; +import { PluginOriginBadge } from '../../plugins/PluginOriginBadge'; import { Annotation } from '../../utils/constants'; -import { isGrafanaRulerRule, isGrafanaRulerRulePaused } from '../../utils/rules'; +import { getRulePluginOrigin, isGrafanaRulerRule, isGrafanaRulerRulePaused } from '../../utils/rules'; import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines'; import { ProvisioningBadge } from '../Provisioning'; @@ -175,13 +176,18 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean, showNe size: showNextEvaluationColumn ? 4 : 5, }, { - id: 'provisioned', + id: 'metadata', label: '', // eslint-disable-next-line react/display-name renderCell: ({ data: rule }) => { const rulerRule = rule.rulerRule; - const isGrafanaManagedRule = isGrafanaRulerRule(rulerRule); + const originMeta = getRulePluginOrigin(rule); + if (originMeta) { + return ; + } + + const isGrafanaManagedRule = isGrafanaRulerRule(rulerRule); if (!isGrafanaManagedRule) { return null; } diff --git a/public/app/features/alerting/unified/hooks/useAbilities.ts b/public/app/features/alerting/unified/hooks/useAbilities.ts index 9906553bf7b..14ffa869fc5 100644 --- a/public/app/features/alerting/unified/hooks/useAbilities.ts +++ b/public/app/features/alerting/unified/hooks/useAbilities.ts @@ -9,7 +9,7 @@ import { alertmanagerApi } from '../api/alertmanagerApi'; import { useAlertmanager } from '../state/AlertmanagerContext'; import { getInstancesPermissions, getNotificationsPermissions, getRulesPermissions } from '../utils/access-control'; import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; -import { isFederatedRuleGroup, isGrafanaRulerRule } from '../utils/rules'; +import { isFederatedRuleGroup, isGrafanaRulerRule, isPluginProvidedRule } from '../utils/rules'; import { useIsRuleEditable } from './useIsRuleEditable'; @@ -147,9 +147,10 @@ export function useAllAlertRuleAbilities(rule: CombinedRule): Abilities = { - [AlertRuleAction.Duplicate]: toAbility(MaybeSupported, rulesPermissions.create), + [AlertRuleAction.Duplicate]: toAbility(duplicateSupported, rulesPermissions.create), [AlertRuleAction.View]: toAbility(AlwaysSupported, rulesPermissions.read), [AlertRuleAction.Update]: [MaybeSupportedUnlessImmutable, isEditable ?? false], [AlertRuleAction.Delete]: [MaybeSupportedUnlessImmutable, isRemovable ?? false], diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 7ac3c3dfb13..fcbed3a177b 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -10,6 +10,8 @@ import { DataSourceJsonData, DataSourcePluginMeta, DataSourceRef, + PluginExtensionLink, + PluginExtensionTypes, PluginMeta, PluginType, ScopedVars, @@ -707,6 +709,18 @@ export function getCloudRule(override?: Partial) { }); } +export function mockPluginLinkExtension(extension: Partial): PluginExtensionLink { + return { + type: PluginExtensionTypes.link, + id: 'plugin-id', + pluginId: 'grafana-test-app', + title: 'Test plugin link', + description: 'Test plugin link', + path: '/test', + ...extension, + }; +} + export function mockAlertWithState(state: GrafanaAlertState, labels?: {}): Alert { return { activeAt: '', annotations: {}, labels: labels || {}, state: state, value: '' }; } diff --git a/public/app/features/alerting/unified/mocks/plugins.ts b/public/app/features/alerting/unified/mocks/plugins.ts index 48c98edb33b..1a97fc39f50 100644 --- a/public/app/features/alerting/unified/mocks/plugins.ts +++ b/public/app/features/alerting/unified/mocks/plugins.ts @@ -1,17 +1,10 @@ import { http, HttpResponse } from 'msw'; -import { SetupServer } from 'msw/lib/node'; import { PluginMeta } from '@grafana/data'; -import { SupportedPlugin } from '../types/pluginBridges'; - -export function mockPluginSettings(server: SetupServer, plugin: SupportedPlugin, response?: PluginMeta) { - server.use( - http.get(`/api/plugins/${plugin}/settings`, () => { - if (response) { - return HttpResponse.json(response); - } - return HttpResponse.json({}, { status: 404 }); - }) +export const pluginsHandler = (pluginsRegistry: Map) => + http.get<{ pluginId: string }>(`/api/plugins/:pluginId/settings`, ({ params: { pluginId } }) => + pluginsRegistry.has(pluginId) + ? HttpResponse.json(pluginsRegistry.get(pluginId)!) + : HttpResponse.json({ message: 'Plugin not found, no installed plugin with that id' }, { status: 404 }) ); -} diff --git a/public/app/features/alerting/unified/plugins/PluginOriginBadge.tsx b/public/app/features/alerting/unified/plugins/PluginOriginBadge.tsx new file mode 100644 index 00000000000..437d56fd192 --- /dev/null +++ b/public/app/features/alerting/unified/plugins/PluginOriginBadge.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { useAsync } from 'react-use'; + +import { Badge, Tooltip } from '@grafana/ui'; + +import { getPluginSettings } from '../../../plugins/pluginSettings'; + +interface PluginOriginBadgeProps { + pluginId: string; +} + +export function PluginOriginBadge({ pluginId }: PluginOriginBadgeProps) { + const { value: pluginMeta } = useAsync(() => getPluginSettings(pluginId)); + + const logo = pluginMeta?.info.logos?.small; + + const badgeIcon = logo ? ( + {pluginMeta?.name} + ) : ( + + ); + + const tooltipContent = pluginMeta + ? `This rule is managed by the ${pluginMeta?.name} plugin` + : `This rule is managed by a plugin`; + + return ( + +
{badgeIcon}
+
+ ); +} diff --git a/public/app/features/alerting/unified/plugins/useRulePluginLinkExtensions.ts b/public/app/features/alerting/unified/plugins/useRulePluginLinkExtensions.ts new file mode 100644 index 00000000000..237dbbe8154 --- /dev/null +++ b/public/app/features/alerting/unified/plugins/useRulePluginLinkExtensions.ts @@ -0,0 +1,92 @@ +import { useMemo } from 'react'; + +import { PluginExtensionPoints } from '@grafana/data'; +import { usePluginLinkExtensions } from '@grafana/runtime'; +import { CombinedRule } from 'app/types/unified-alerting'; +import { PromRuleType } from 'app/types/unified-alerting-dto'; + +import { getRulePluginOrigin } from '../utils/rules'; + +interface BaseRuleExtensionContext { + name: string; + namespace: string; + group: string; + expression: string; + labels: Record; +} + +export interface AlertingRuleExtensionContext extends BaseRuleExtensionContext { + annotations: Record; +} + +export interface RecordingRuleExtensionContext extends BaseRuleExtensionContext {} + +export function useRulePluginLinkExtension(rule: CombinedRule) { + const ruleExtensionPoint = useRuleExtensionPoint(rule); + const { extensions } = usePluginLinkExtensions(ruleExtensionPoint); + + const ruleOrigin = getRulePluginOrigin(rule); + const ruleType = rule.promRule?.type; + if (!ruleOrigin || !ruleType) { + return []; + } + + const { pluginId } = ruleOrigin; + + return extensions.filter((extension) => extension.pluginId === pluginId); +} + +export interface PluginRuleExtensionParam { + pluginId: string; + rule: CombinedRule; +} + +interface AlertingRuleExtensionPoint { + extensionPointId: PluginExtensionPoints.AlertingAlertingRuleAction; + context: AlertingRuleExtensionContext; +} + +interface RecordingRuleExtensionPoint { + extensionPointId: PluginExtensionPoints.AlertingRecordingRuleAction; + context: RecordingRuleExtensionContext; +} + +interface EmptyExtensionPoint { + extensionPointId: ''; +} + +type RuleExtensionPoint = AlertingRuleExtensionPoint | RecordingRuleExtensionPoint | EmptyExtensionPoint; + +function useRuleExtensionPoint(rule: CombinedRule): RuleExtensionPoint { + return useMemo(() => { + const ruleType = rule.promRule?.type; + + switch (ruleType) { + case PromRuleType.Alerting: + return { + extensionPointId: PluginExtensionPoints.AlertingAlertingRuleAction, + context: { + name: rule.name, + namespace: rule.namespace.name, + group: rule.group.name, + expression: rule.query, + labels: rule.labels, + annotations: rule.annotations, + }, + }; + case PromRuleType.Recording: + return { + extensionPointId: PluginExtensionPoints.AlertingRecordingRuleAction, + context: { + name: rule.name, + namespace: rule.namespace.name, + group: rule.group.name, + expression: rule.query, + labels: rule.labels, + }, + }; + default: + return { extensionPointId: '' }; + } + }, [rule]); +} diff --git a/public/app/features/alerting/unified/testSetup/plugins.ts b/public/app/features/alerting/unified/testSetup/plugins.ts new file mode 100644 index 00000000000..1ef1b4fae87 --- /dev/null +++ b/public/app/features/alerting/unified/testSetup/plugins.ts @@ -0,0 +1,97 @@ +import { RequestHandler } from 'msw'; + +import { PluginMeta, PluginType } from '@grafana/data'; +import { config } from '@grafana/runtime'; + +import { pluginsHandler } from '../mocks/plugins'; + +export function setupPlugins(...plugins: PluginMeta[]): { apiHandlers: RequestHandler[] } { + const pluginsRegistry = new Map(); + plugins.forEach((plugin) => pluginsRegistry.set(plugin.id, plugin)); + + pluginsRegistry.forEach((plugin) => { + config.apps[plugin.id] = { + id: plugin.id, + path: plugin.baseUrl, + preload: true, + version: plugin.info.version, + angular: plugin.angular ?? { detected: false, hideDeprecation: false }, + }; + }); + + return { + apiHandlers: [pluginsHandler(pluginsRegistry)], + }; +} + +export const plugins: Record = { + slo: { + id: 'grafana-slo-app', + name: 'SLO dashboard', + type: PluginType.app, + enabled: true, + info: { + author: { + name: 'Grafana Labs', + url: '', + }, + description: 'Create and manage Service Level Objectives', + links: [], + logos: { + small: 'public/plugins/grafana-slo-app/img/logo.svg', + large: 'public/plugins/grafana-slo-app/img/logo.svg', + }, + screenshots: [], + version: 'local-dev', + updated: '2024-04-09', + }, + module: 'public/plugins/grafana-slo-app/module.js', + baseUrl: 'public/plugins/grafana-slo-app', + }, + incident: { + id: 'grafana-incident-app', + name: 'Incident management', + type: PluginType.app, + enabled: true, + info: { + author: { + name: 'Grafana Labs', + url: '', + }, + description: 'Incident management', + links: [], + logos: { + small: 'public/plugins/grafana-incident-app/img/logo.svg', + large: 'public/plugins/grafana-incident-app/img/logo.svg', + }, + screenshots: [], + version: 'local-dev', + updated: '2024-04-09', + }, + module: 'public/plugins/grafana-incident-app/module.js', + baseUrl: 'public/plugins/grafana-incident-app', + }, + asserts: { + id: 'grafana-asserts-app', + name: 'Asserts', + type: PluginType.app, + enabled: true, + info: { + author: { + name: 'Grafana Labs', + url: '', + }, + description: 'Asserts', + links: [], + logos: { + small: 'public/plugins/grafana-asserts-app/img/logo.svg', + large: 'public/plugins/grafana-asserts-app/img/logo.svg', + }, + screenshots: [], + version: 'local-dev', + updated: '2024-04-09', + }, + module: 'public/plugins/grafana-asserts-app/module.js', + baseUrl: 'public/plugins/grafana-asserts-app', + }, +}; diff --git a/public/app/features/alerting/unified/utils/labels.ts b/public/app/features/alerting/unified/utils/labels.ts index 49da17ab97a..7c89af0ea13 100644 --- a/public/app/features/alerting/unified/utils/labels.ts +++ b/public/app/features/alerting/unified/utils/labels.ts @@ -32,3 +32,11 @@ export function arrayKeyValuesToObject( return labelsObject; } + +export const GRAFANA_ORIGIN_LABEL = '__grafana_origin'; + +export function isPrivateLabelKey(labelKey: string) { + return (labelKey.startsWith('__') && labelKey.endsWith('__')) || labelKey === GRAFANA_ORIGIN_LABEL; +} + +export const isPrivateLabel = ([key, _]: [string, string]) => isPrivateLabelKey(key); diff --git a/public/app/features/alerting/unified/utils/matchers.ts b/public/app/features/alerting/unified/utils/matchers.ts index 2cac197f8d9..a596a318da0 100644 --- a/public/app/features/alerting/unified/utils/matchers.ts +++ b/public/app/features/alerting/unified/utils/matchers.ts @@ -11,6 +11,8 @@ import { Matcher, MatcherOperator, ObjectMatcher, Route } from 'app/plugins/data import { Labels } from '../../../../types/unified-alerting-dto'; +import { isPrivateLabelKey } from './labels'; + const matcherOperators = [ MatcherOperator.regex, MatcherOperator.notRegex, @@ -91,9 +93,7 @@ export function parseQueryParamMatchers(matcherPairs: string[]): Matcher[] { } export const getMatcherQueryParams = (labels: Labels) => { - const validMatcherLabels = Object.entries(labels).filter( - ([labelKey]) => !(labelKey.startsWith('__') && labelKey.endsWith('__')) - ); + const validMatcherLabels = Object.entries(labels).filter(([labelKey]) => !isPrivateLabelKey(labelKey)); const matcherUrlParams = new URLSearchParams(); validMatcherLabels.forEach(([labelKey, labelValue]) => diff --git a/public/app/features/alerting/unified/utils/rules.test.ts b/public/app/features/alerting/unified/utils/rules.test.ts new file mode 100644 index 00000000000..7ec2349f298 --- /dev/null +++ b/public/app/features/alerting/unified/utils/rules.test.ts @@ -0,0 +1,45 @@ +import { config } from '@grafana/runtime'; + +import { mockCombinedRule } from '../mocks'; + +import { GRAFANA_ORIGIN_LABEL } from './labels'; +import { getRulePluginOrigin } from './rules'; + +describe('getRuleOrigin', () => { + it('returns undefined when no origin label is present', () => { + const rule = mockCombinedRule({ + labels: {}, + }); + expect(getRulePluginOrigin(rule)).toBeUndefined(); + }); + + it('returns undefined when origin label does not match expected format', () => { + const rule = mockCombinedRule({ + labels: { [GRAFANA_ORIGIN_LABEL]: 'invalid_format' }, + }); + expect(getRulePluginOrigin(rule)).toBeUndefined(); + }); + + it('returns undefined when plugin is not installed', () => { + const rule = mockCombinedRule({ + labels: { [GRAFANA_ORIGIN_LABEL]: 'plugin/uninstalled_plugin' }, + }); + expect(getRulePluginOrigin(rule)).toBeUndefined(); + }); + + it('returns pluginId when origin label matches expected format and plugin is installed', () => { + config.apps = { + installed_plugin: { + id: 'installed_plugin', + version: '', + path: '', + preload: true, + angular: { detected: false, hideDeprecation: false }, + }, + }; + const rule = mockCombinedRule({ + labels: { [GRAFANA_ORIGIN_LABEL]: 'plugin/installed_plugin' }, + }); + expect(getRulePluginOrigin(rule)).toEqual({ pluginId: 'installed_plugin' }); + }); +}); diff --git a/public/app/features/alerting/unified/utils/rules.ts b/public/app/features/alerting/unified/utils/rules.ts index 49481acb971..248774818e2 100644 --- a/public/app/features/alerting/unified/utils/rules.ts +++ b/public/app/features/alerting/unified/utils/rules.ts @@ -1,10 +1,12 @@ import { capitalize } from 'lodash'; import { AlertState } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { Alert, AlertingRule, CloudRuleIdentifier, + CombinedRule, CombinedRuleGroup, CombinedRuleWithLocation, GrafanaRuleIdentifier, @@ -33,6 +35,7 @@ import { RuleHealth } from '../search/rulesSearchParser'; import { RULER_NOT_SUPPORTED_MSG } from './constants'; import { getRulesSourceName } from './datasource'; +import { GRAFANA_ORIGIN_LABEL } from './labels'; import { AsyncRequestState } from './redux'; import { safeParsePrometheusDuration } from './time'; @@ -100,6 +103,41 @@ export function getRuleHealth(health: string): RuleHealth | undefined { } } +export interface RulePluginOrigin { + pluginId: string; +} + +export function getRulePluginOrigin(rule: CombinedRule): RulePluginOrigin | undefined { + // com.grafana.origin=plugin/ + // Prom and Mimir do not support dots in label names 😔 + const origin = rule.labels[GRAFANA_ORIGIN_LABEL]; + if (!origin) { + return undefined; + } + + const match = origin.match(/^plugin\/(?.+)$/); + if (!match?.groups) { + return undefined; + } + + const pluginId = match.groups['pluginId']; + const pluginInstalled = isPluginInstalled(pluginId); + + if (!pluginInstalled) { + return undefined; + } + + return { pluginId }; +} + +function isPluginInstalled(pluginId: string) { + return Boolean(config.apps[pluginId]); +} + +export function isPluginProvidedRule(rule: CombinedRule): boolean { + return Boolean(getRulePluginOrigin(rule)); +} + export function alertStateToReadable(state: PromAlertingRuleState | GrafanaAlertStateWithReason | AlertState): string { if (state === PromAlertingRuleState.Inactive) { return 'Normal'; diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx index 06320963aa8..ea559badb2a 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.test.tsx @@ -6,7 +6,7 @@ import { byTestId } from 'testing-library-selector'; import { DataSourceApi } from '@grafana/data'; import { PromOptions, PrometheusDatasource } from '@grafana/prometheus'; -import { locationService, setDataSourceSrv } from '@grafana/runtime'; +import { locationService, setDataSourceSrv, setPluginExtensionsHook } from '@grafana/runtime'; import { backendSrv } from 'app/core/services/backend_srv'; import { fetchRules } from 'app/features/alerting/unified/api/prometheus'; import { fetchRulerRules } from 'app/features/alerting/unified/api/ruler'; @@ -48,6 +48,11 @@ jest.mock('app/features/alerting/unified/api/ruler'); jest.spyOn(config, 'getAllDataSources'); jest.spyOn(ruleActionButtons, 'matchesWidth').mockReturnValue(false); +setPluginExtensionsHook(() => ({ + extensions: [], + isLoading: false, +})); + const dataSources = { prometheus: mockDataSource({ name: 'Prometheus', diff --git a/public/app/plugins/panel/alertlist/GroupByWithLoading.tsx b/public/app/plugins/panel/alertlist/GroupByWithLoading.tsx index ed5713720f7..dcf90a7f5b0 100644 --- a/public/app/plugins/panel/alertlist/GroupByWithLoading.tsx +++ b/public/app/plugins/panel/alertlist/GroupByWithLoading.tsx @@ -14,8 +14,7 @@ import { AlertingRule } from 'app/types/unified-alerting'; import { PromRuleType } from 'app/types/unified-alerting-dto'; import { fetchPromRulesAction } from '../../../features/alerting/unified/state/actions'; - -import { isPrivateLabel } from './util'; +import { isPrivateLabelKey } from '../../../features/alerting/unified/utils/labels'; interface Props { id: string; @@ -56,7 +55,7 @@ export const GroupBy = (props: Props) => { .flatMap((group) => group.rules.filter((rule): rule is AlertingRule => rule.type === PromRuleType.Alerting)) .flatMap((rule) => rule.alerts ?? []) .map((alert) => Object.keys(alert.labels ?? {})) - .flatMap((labels) => labels.filter(isPrivateLabel)); + .flatMap((labels) => labels.filter((label) => !isPrivateLabelKey(label))); return uniq(allLabels); }, [allRequestsReady, promRulesByDatasource]); diff --git a/public/app/plugins/panel/alertlist/util.ts b/public/app/plugins/panel/alertlist/util.ts index 47479c7004e..5acf905d27c 100644 --- a/public/app/plugins/panel/alertlist/util.ts +++ b/public/app/plugins/panel/alertlist/util.ts @@ -36,7 +36,3 @@ export function filterAlerts( ); }); } - -export function isPrivateLabel(label: string) { - return !(label.startsWith('__') && label.endsWith('__')); -} From 6dbc44920c4034f928c68479965f4e202d5ba70f Mon Sep 17 00:00:00 2001 From: Tim Mulqueen Date: Tue, 30 Apr 2024 09:54:25 +0100 Subject: [PATCH 204/222] Dashboard Scene - Variable Fix: cancel out margin-bottom of placeholder in loading state (#87107) fix: cancel out margin-bottom of placeholder in loading state --- .../settings/variables/VariableEditorForm.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx b/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx index d04e228acc4..e94b5b09044 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableEditorForm.tsx @@ -152,7 +152,11 @@ export function VariableEditorForm({ data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.General.submitButton} onClick={onRunQuery} > - {runQueryState.loading ? : `Run query`} + {runQueryState.loading ? ( + + ) : ( + `Run query` + )} )} @@ -165,4 +169,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ buttonContainer: css({ marginTop: theme.spacing(2), }), + loadingPlaceHolder: css({ + marginBottom: 0, + }), }); From 9203f84bc83a9f710749c47a20130ebd3a63f337 Mon Sep 17 00:00:00 2001 From: antonio <45235678+tonypowa@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:06:25 +0200 Subject: [PATCH 205/222] docs / alerting / fundamentals / templates (#86983) * docs / alerting / fundamentals / templates * renamed and adjusted front matter * pretty * frontmatter * admonition fix * admo * restructuring * Update docs/sources/alerting/fundamentals/notifications/templates.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/alerting/fundamentals/notifications/templates.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/alerting/fundamentals/notifications/templates.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/alerting/fundamentals/notifications/templates.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Update docs/sources/alerting/fundamentals/notifications/templates.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * amended admonition --------- Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> --- .../notifications/message-templating.md | 51 ------ .../fundamentals/notifications/templates.md | 159 ++++++++++++++++++ 2 files changed, 159 insertions(+), 51 deletions(-) delete mode 100644 docs/sources/alerting/fundamentals/notifications/message-templating.md create mode 100644 docs/sources/alerting/fundamentals/notifications/templates.md diff --git a/docs/sources/alerting/fundamentals/notifications/message-templating.md b/docs/sources/alerting/fundamentals/notifications/message-templating.md deleted file mode 100644 index 6f938729129..00000000000 --- a/docs/sources/alerting/fundamentals/notifications/message-templating.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -aliases: - - ../../contact-points/message-templating/ # /docs/grafana//alerting/contact-points/message-templating/ - - ../../alert-rules/message-templating/ # /docs/grafana//alerting/alert-rules/message-templating/ - - ../../unified-alerting/message-templating/ # /docs/grafana//alerting/unified-alerting/message-templating/ -canonical: https://grafana.com/docs/grafana/latest/alerting/fundamentals/notifications/message-templating/ -description: Learn about templates -keywords: - - grafana - - alerting - - guide - - contact point - - templating -labels: - products: - - cloud - - enterprise - - oss -title: Templates -weight: 114 ---- - -## Templates - -Use templating to customize, format, and reuse alert notification messages. Create more flexible and informative alert notification messages by incorporating dynamic content, such as metric values, labels, and other contextual information. - -In Grafana, there are two ways to template your alert notification messages: - -1. Labels and annotations - - - Template labels and annotations in alert rules. - - Labels and annotations contain information about an alert. - - Labels are used to differentiate an alert from all other alerts, while annotations are used to add additional information to an existing alert. - -2. Notification templates - - - Template notifications in contact points. - - Add notification templates to contact points for reuse and consistent messaging in your notifications. - - Use notification templates to change the title, message, and format of the message in your notifications. - -This diagram illustrates the entire process of templating, from the creation of labels and annotations in alert rules or notification templates in contact points, to what they look like when exported and applied in your alert notification messages. - -{{< figure src="/media/docs/alerting/grafana-templating-diagram-2.jpg" max-width="1200px" caption="How Templating works" >}} - -In this diagram: - -- **Monitored Application**: A web server, database, or any other service generating metrics. For example, it could be an NGINX server providing metrics about request rates, response times, and so on. -- **Prometheus**: Prometheus collects metrics from the monitored application. For example, it might scrape metrics from the NGINX server, including labels like instance (the server hostname) and job (the service name). -- **Grafana**: Grafana queries Prometheus to retrieve metrics data. For example, you might create an alert rule to monitor NGINX request rates over time, and template labels or annotations based on the instance label. -- **Alertmanager**: Part of the Prometheus ecosystem, Alertmanager handles alert notifications. For example, if the request rate exceeds a certain threshold on a particular NGINX server, Alertmanager can send an alert notification to, for example, Slack or email, including the server name and the exceeded threshold (the instance label will be interpolated, and the actual server name will appear in the alert notification). -- **Alert notification**: When an alert rule condition is met, Alertmanager sends a notification to various channels such as Slack, Grafana OnCall, etc. These notifications can include information from the labels associated with the alerting rule. For example, if an alert triggers due to high CPU usage on a specific server, the notification message can include details like server name (instance label), disk usage percentage, and the threshold that was exceeded. diff --git a/docs/sources/alerting/fundamentals/notifications/templates.md b/docs/sources/alerting/fundamentals/notifications/templates.md new file mode 100644 index 00000000000..dacd9a5310f --- /dev/null +++ b/docs/sources/alerting/fundamentals/notifications/templates.md @@ -0,0 +1,159 @@ +--- +aliases: + - ../../contact-points/message-templating/ # /docs/grafana//alerting/contact-points/message-templating/ + - ../../alert-rules/message-templating/ # /docs/grafana//alerting/alert-rules/message-templating/ + - ../../unified-alerting/message-templating/ # /docs/grafana//alerting/unified-alerting/message-templating/ +canonical: https://grafana.com/docs/grafana/latest/alerting/fundamentals/notifications/templates/ +description: Learn about templates +keywords: + - grafana + - alerting + - guide + - contact point + - templating +labels: + products: + - cloud + - enterprise + - oss +title: Templates +weight: 114 +--- + +# Templates + +Use templating to customize, format, and reuse alert notification messages. Create more flexible and informative alert notification messages by incorporating dynamic content, such as metric values, labels, and other contextual information. + +In Grafana, there are two ways to template your alert notification messages: + +1. Labels and annotations + + - Template labels and annotations in alert rules. + - Labels and annotations contain information about an alert. + - Labels are used to differentiate an alert from all other alerts, while annotations are used to add additional information to an existing alert. + +2. Notification templates + + - Template notifications in contact points. + - Add notification templates to contact points for reuse and consistent messaging in your notifications. + - Use notification templates to change the title, message, and format of the message in your notifications. + +This diagram illustrates the entire process of templating, from the creation of labels and annotations in alert rules or notification templates in contact points, to what they look like when exported and applied in your alert notification messages. + +{{< figure src="/media/docs/alerting/grafana-templating-diagram-2.jpg" max-width="1200px" caption="How Templating works" >}} + +In this diagram: + +- **Monitored Application**: A web server, database, or any other service generating metrics. For example, it could be an NGINX server providing metrics about request rates, response times, and so on. +- **Prometheus**: Prometheus collects metrics from the monitored application. For example, it might scrape metrics from the NGINX server, including labels like instance (the server hostname) and job (the service name). +- **Grafana**: Grafana queries Prometheus to retrieve metrics data. For example, you might create an alert rule to monitor NGINX request rates over time, and template labels or annotations based on the instance label. +- **Alertmanager**: Part of the Prometheus ecosystem, Alertmanager handles alert notifications. For example, if the request rate exceeds a certain threshold on a particular NGINX server, Alertmanager can send an alert notification to, for example, Slack or email, including the server name and the exceeded threshold (the instance label will be interpolated, and the actual server name will appear in the alert notification). +- **Alert notification**: When an alert rule condition is met, Alertmanager sends a notification to various channels such as Slack, Grafana OnCall, etc. These notifications can include information from the labels associated with the alerting rule. For example, if an alert triggers due to high CPU usage on a specific server, the notification message can include details like server name (instance label), disk usage percentage, and the threshold that was exceeded. + +## Labels and annotations + +Labels and annotations contain information about an alert. Labels are used to differentiate an alert from all other alerts, while annotations are used to add additional information to an existing alert. + +### Template labels + +Label templates are applied in the alert rule itself (i.e. in the Configure labels and notifications section of an alert). + +{{}} +Think about templating labels when you need to improve or change how alerts are uniquely identified. This is especially helpful if the labels you get from your query aren't detailed enough. Keep in mind that it's better to keep long sentences for summaries and descriptions. Also, avoid using the query's value in labels because it may result in the creation of many alerts when you actually only need one. +{{}} + +Templating can be applied by using variables and functions. These variables can represent dynamic values retrieved from your data queries. + +{{}} +In Grafana templating, the $ and . symbols are used to reference variables and their properties. You can reference variables directly in your alert rule definitions using the $ symbol followed by the variable name. Similarly, you can access properties of variables using the dot (.) notation within alert rule definitions. +{{}} + +Here are some commonly used built-in [variables][variables-label-annotation] to interact with the name and value of labels in Grafana alerting: + +- The `$labels` variable, which contains all labels from the query. + + For example, let's say you have an alert rule that triggers when the CPU usage exceeds a certain threshold. You want to create annotations that provide additional context when this alert is triggered, such as including the specific server that experienced the high CPU usage. + + The host {{ index $labels "instance" }} has exceeded 80% CPU usage for the last 5 minutes + + The outcome of this template would print: + + The host instance 1 has exceeded 80% CPU usage for the last 5 minutes + +- The `$value` variable, which is a string containing the labels and values of all instant queries; threshold, reduce and math expressions, and classic conditions in the alert rule. + + In the context of the previous example, $value variable would write something like this: + + CPU usage for {{ index $labels "instance" }} has exceeded 80% for the last 5 minutes: {{ $value }} + + The outcome of this template would print: + + CPU usage for instance1 has exceeded 80% for the last 5 minutes: [ var='A' labels={instance=instance1} value=81.234 ] + +- The `$values` variable is a table containing the labels and floating point values of all instant queries and expressions, indexed by their Ref IDs (i.e. the id that identifies the query or expression. By default the Red ID of the query is “A”). + + Given an alert with the labels instance=server1 and an instant query with the value 81.2345, would write like this: + + CPU usage for {{ index $labels "instance" }} has exceeded 80% for the last 5 minutes: {{ index $values "A" }} + + And it would print: + + CPU usage for instance1 has exceeded 80% for the last 5 minutes: 81.2345 + +### Template annotations + +Both labels and annotations have the same structure: a set of named values; however their intended uses are different. The purpose of annotations is to add additional information to existing alerts. + +There are a number of suggested annotations in Grafana such as `description`, `summary`, `runbook_url`, `dashboardUId` and `panelId`. Like labels, annotations must have a name, and their value can contain a combination of text and template code that is evaluated when an alert is fired. + +Here is an example of templating an annotation in the context of an alert rule. The text/template is added into the Add annotations section. + + CPU usage for {{ index $labels "instance" }} has exceeded 80% for the last 5 minutes + +The outcome of this template would print + + CPU usage for Instance 1 has exceeded 80% for the last 5 minutes + +### Template notifications + +Notification templates represent the alternative approach to templating designed for reusing templates. Notifications are messages to inform users about events or conditions triggered by alerts. You can create reusable notification templates to customize the content and format of alert notifications. Variables, labels, or other context-specific details can be added to the templates to dynamically insert information like metric values. + +Here is an example of a notification template: + +```go +{ define "alerts.message" -}} +{{ if .Alerts.Firing -}} +{{ len .Alerts.Firing }} firing alert(s) +{{ template "alerts.summarize" .Alerts.Firing }} +{{- end }} +{{- if .Alerts.Resolved -}} +{{ len .Alerts.Resolved }} resolved alert(s) +{{ template "alerts.summarize" .Alerts.Resolved }} +{{- end }} +{{- end }} + +{{ define "alerts.summarize" -}} +{{ range . -}} +- {{ index .Annotations "summary" }} +{{ end }} +{{ end }} +``` + +This is the message you would receive in your contact point: + + 1 firing alert(s) + - The database server db1 has exceeded 75% of available disk space. Disk space used is 76%, please resize the disk size within the next 24 hours + + 1 resolved alert(s) + - The web server web1 has been responding to 5% of HTTP requests with 5xx errors for the last 5 minutes + +Once the template is created, you need to make reference to it in your **Contact point** (in the Optional [contact point] settings) . + +{{}} +It's not recommended to include individual alert information within notification templates. Instead, it's more effective to incorporate such details within the rule using labels and annotations. +{{}} + +{{% docs/reference %}} +[variables-label-annotation]: "/docs/grafana/ -> /docs/grafana//alerting/alerting-rules/templating-labels-annotations" +[variables-label-annotation]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templating-labels-annotations" +{{% /docs/reference %}} From ec6f59a678ba97d43353a2bcfda1c9b241c55e31 Mon Sep 17 00:00:00 2001 From: Arati R <33031346+suntala@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:14:33 +0200 Subject: [PATCH 206/222] Chore: Update protoc-gen-go (#87116) Update protoc-gen-go --- pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go | 4 ++-- .../backendplugin/pluginextensionv2/rendererv2_grpc.pb.go | 2 +- pkg/plugins/backendplugin/pluginextensionv2/sanitizer.pb.go | 4 ++-- .../backendplugin/pluginextensionv2/sanitizer_grpc.pb.go | 2 +- .../backendplugin/secretsmanagerplugin/secretsmanager.pb.go | 4 ++-- .../secretsmanagerplugin/secretsmanager_grpc.pb.go | 2 +- pkg/services/store/entity/entity.pb.go | 4 ++-- pkg/services/store/entity/entity_grpc.pb.go | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go b/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go index 4f235a4f61d..ab2a9b8e673 100644 --- a/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go +++ b/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 -// protoc v4.25.2 +// protoc-gen-go v1.33.0 +// protoc v5.26.1 // source: rendererv2.proto package pluginextensionv2 diff --git a/pkg/plugins/backendplugin/pluginextensionv2/rendererv2_grpc.pb.go b/pkg/plugins/backendplugin/pluginextensionv2/rendererv2_grpc.pb.go index 7625f9655e2..5875a69e090 100644 --- a/pkg/plugins/backendplugin/pluginextensionv2/rendererv2_grpc.pb.go +++ b/pkg/plugins/backendplugin/pluginextensionv2/rendererv2_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.2 +// - protoc v5.26.1 // source: rendererv2.proto package pluginextensionv2 diff --git a/pkg/plugins/backendplugin/pluginextensionv2/sanitizer.pb.go b/pkg/plugins/backendplugin/pluginextensionv2/sanitizer.pb.go index a420647a687..b464bbdc65c 100644 --- a/pkg/plugins/backendplugin/pluginextensionv2/sanitizer.pb.go +++ b/pkg/plugins/backendplugin/pluginextensionv2/sanitizer.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 -// protoc v4.25.2 +// protoc-gen-go v1.33.0 +// protoc v5.26.1 // source: sanitizer.proto package pluginextensionv2 diff --git a/pkg/plugins/backendplugin/pluginextensionv2/sanitizer_grpc.pb.go b/pkg/plugins/backendplugin/pluginextensionv2/sanitizer_grpc.pb.go index 80a3f117df3..a56ffbffc82 100644 --- a/pkg/plugins/backendplugin/pluginextensionv2/sanitizer_grpc.pb.go +++ b/pkg/plugins/backendplugin/pluginextensionv2/sanitizer_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.2 +// - protoc v5.26.1 // source: sanitizer.proto package pluginextensionv2 diff --git a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager.pb.go b/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager.pb.go index 777de3c42f2..09afbab3654 100644 --- a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager.pb.go +++ b/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 -// protoc v4.25.2 +// protoc-gen-go v1.33.0 +// protoc v5.26.1 // source: secretsmanager.proto package secretsmanagerplugin diff --git a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager_grpc.pb.go b/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager_grpc.pb.go index 264020b216e..945d6fa9f7f 100644 --- a/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager_grpc.pb.go +++ b/pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.2 +// - protoc v5.26.1 // source: secretsmanager.proto package secretsmanagerplugin diff --git a/pkg/services/store/entity/entity.pb.go b/pkg/services/store/entity/entity.pb.go index 76a04593bd1..1674b8d7fdc 100644 --- a/pkg/services/store/entity/entity.pb.go +++ b/pkg/services/store/entity/entity.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 -// protoc v4.25.2 +// protoc-gen-go v1.33.0 +// protoc v5.26.1 // source: entity.proto package entity diff --git a/pkg/services/store/entity/entity_grpc.pb.go b/pkg/services/store/entity/entity_grpc.pb.go index e76302c91dc..c1ab389ca2d 100644 --- a/pkg/services/store/entity/entity_grpc.pb.go +++ b/pkg/services/store/entity/entity_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.2 +// - protoc v5.26.1 // source: entity.proto package entity From 7f1b2ef20545f2fa6aa40f87ab69330c8a53d625 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Tue, 30 Apr 2024 13:04:58 +0200 Subject: [PATCH 207/222] Select: Add data-testid to Input (#87105) * Select: Add custom input component * Forward data-testid * Add input selector * Props check --- e2e/various-suite/loki-query-builder.spec.ts | 4 ++-- .../src/selectors/components.ts | 1 + .../src/components/Select/CustomInput.tsx | 15 +++++++++++++++ .../src/components/Select/SelectBase.tsx | 2 ++ 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 packages/grafana-ui/src/components/Select/CustomInput.tsx diff --git a/e2e/various-suite/loki-query-builder.spec.ts b/e2e/various-suite/loki-query-builder.spec.ts index 1707a32dd85..45b527fca5b 100644 --- a/e2e/various-suite/loki-query-builder.spec.ts +++ b/e2e/various-suite/loki-query-builder.spec.ts @@ -72,9 +72,9 @@ describe('Loki query builder', () => { // Add labels to remove error e2e.components.QueryBuilder.labelSelect().should('be.visible').click(); // wait until labels are loaded and set on the component before starting to type - e2e.components.QueryBuilder.labelSelect().children('div').children('input').type('i'); + e2e.components.QueryBuilder.inputSelect().type('i'); cy.wait('@labelsRequest'); - e2e.components.QueryBuilder.labelSelect().children('div').children('input').type('nstance{enter}'); + e2e.components.QueryBuilder.inputSelect().type('nstance{enter}'); e2e.components.QueryBuilder.matchOperatorSelect() .should('be.visible') .click({ force: true }) diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index a2142e464fb..9d73f68f3ff 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -452,6 +452,7 @@ export const Components = { QueryBuilder: { queryPatterns: 'data-testid Query patterns', labelSelect: 'data-testid Select label', + inputSelect: 'data-testid Select label-input', valueSelect: 'data-testid Select value', matchOperatorSelect: 'data-testid Select match operator', }, diff --git a/packages/grafana-ui/src/components/Select/CustomInput.tsx b/packages/grafana-ui/src/components/Select/CustomInput.tsx new file mode 100644 index 00000000000..5531b314ec2 --- /dev/null +++ b/packages/grafana-ui/src/components/Select/CustomInput.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { components, InputProps } from 'react-select'; + +/** + * Custom input component for react-select to add data-testid attribute + */ +export const CustomInput = (props: InputProps) => { + let testId; + + if ('data-testid' in props.selectProps && props.selectProps['data-testid']) { + testId = props.selectProps['data-testid'] + '-input'; + } + + return ; +}; diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index 487f1a118ea..a762fd6a9a4 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -11,6 +11,7 @@ import { useTheme2 } from '../../themes'; import { Icon } from '../Icon/Icon'; import { Spinner } from '../Spinner/Spinner'; +import { CustomInput } from './CustomInput'; import { DropdownIndicator } from './DropdownIndicator'; import { IndicatorsContainer } from './IndicatorsContainer'; import { InputControl } from './InputControl'; @@ -330,6 +331,7 @@ export function SelectBase({ SelectContainer, MultiValueContainer: MultiValueContainer, MultiValueRemove: !disabled ? MultiValueRemove : () => null, + Input: CustomInput, ...components, }} styles={selectStyles} From a2cba3d0b5ae4f1675f5c16f48a1b4f3a250724f Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Tue, 30 Apr 2024 13:15:56 +0200 Subject: [PATCH 208/222] User: Add tracing (#87028) * Inject tracer in tests * Annotate with traces Co-authored-by: Gabriel MABILLE --- pkg/api/folder_bench_test.go | 5 +- pkg/api/org_users_test.go | 6 +- pkg/api/user_test.go | 21 ++- .../commands/conflict_user_command.go | 37 +++-- .../accesscontrol/database/database_test.go | 25 +-- .../resourcepermissions/api_test.go | 37 ++--- .../resourcepermissions/service_test.go | 36 +++-- .../resourcepermissions/store_bench_test.go | 4 +- .../resourcepermissions/store_test.go | 6 +- .../database/database_folder_test.go | 42 +++-- .../libraryelements/libraryelements_test.go | 8 +- .../librarypanels/librarypanels_test.go | 5 +- pkg/services/org/orgimpl/store_test.go | 6 +- .../queryhistory/queryhistory_test.go | 6 +- pkg/services/quota/quotaimpl/quota_test.go | 5 +- .../serviceaccounts/database/store_test.go | 6 +- pkg/services/serviceaccounts/tests/common.go | 11 +- pkg/services/stats/statsimpl/stats_test.go | 5 +- pkg/services/team/teamimpl/store_test.go | 16 +- pkg/services/user/userimpl/store_test.go | 16 +- pkg/services/user/userimpl/user.go | 153 ++++++++++++------ pkg/services/user/userimpl/user_test.go | 62 +++---- .../api/alerting/api_alertmanager_test.go | 6 +- pkg/tests/api/correlations/common_test.go | 6 +- .../api/dashboards/api_dashboards_test.go | 6 +- pkg/tests/api/folders/api_folder_test.go | 6 +- pkg/tests/api/plugins/api_plugins_test.go | 6 +- pkg/tests/api/stats/admin_test.go | 6 +- pkg/tests/apis/helper.go | 5 +- pkg/tests/testinfra/testinfra.go | 5 +- pkg/tests/utils.go | 6 +- 31 files changed, 368 insertions(+), 202 deletions(-) diff --git a/pkg/api/folder_bench_test.go b/pkg/api/folder_bench_test.go index 19db1a09322..7ff5c315ea8 100644 --- a/pkg/api/folder_bench_test.go +++ b/pkg/api/folder_bench_test.go @@ -214,7 +214,10 @@ func setupDB(b testing.TB) benchScenario { require.NoError(b, err) cache := localcache.ProvideService() - userSvc, err := userimpl.ProvideService(db, orgService, cfg, teamSvc, cache, "atest.FakeQuotaService{}, bundleregistry.ProvideService()) + userSvc, err := userimpl.ProvideService( + db, orgService, cfg, teamSvc, cache, tracing.InitializeTracerForTest(), + "atest.FakeQuotaService{}, bundleregistry.ProvideService(), + ) require.NoError(b, err) var orgID int64 = 1 diff --git a/pkg/api/org_users_test.go b/pkg/api/org_users_test.go index 94dd640736b..f3a3834b22f 100644 --- a/pkg/api/org_users_test.go +++ b/pkg/api/org_users_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db/dbtest" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social/socialtest" "github.com/grafana/grafana/pkg/models/roletype" @@ -43,7 +44,10 @@ func setUpGetOrgUsersDB(t *testing.T, sqlStore db.DB, cfg *setting.Cfg) { quotaService := quotaimpl.ProvideService(sqlStore, cfg) orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sqlStore, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + sqlStore, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) id, err := orgService.GetOrCreate(context.Background(), "testOrg") diff --git a/pkg/api/user_test.go b/pkg/api/user_test.go index 5bd8bbd2b68..be9f4f129a2 100644 --- a/pkg/api/user_test.go +++ b/pkg/api/user_test.go @@ -21,6 +21,7 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db/dbtest" "github.com/grafana/grafana/pkg/infra/remotecache" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social/socialtest" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -80,7 +81,10 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { hs.authInfoService = srv orgSvc, err := orgimpl.ProvideService(sqlStore, settings, quotatest.New(false, nil)) require.NoError(t, err) - userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, sc.cfg, nil, nil, quotatest.New(false, nil), supportbundlestest.NewFakeBundleService()) + userSvc, err := userimpl.ProvideService( + sqlStore, orgSvc, sc.cfg, nil, nil, tracing.InitializeTracerForTest(), + quotatest.New(false, nil), supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) hs.userService = userSvc @@ -150,7 +154,10 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { } orgSvc, err := orgimpl.ProvideService(sqlStore, sc.cfg, quotatest.New(false, nil)) require.NoError(t, err) - userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, sc.cfg, nil, nil, quotatest.New(false, nil), supportbundlestest.NewFakeBundleService()) + userSvc, err := userimpl.ProvideService( + sqlStore, orgSvc, sc.cfg, nil, nil, tracing.InitializeTracerForTest(), + quotatest.New(false, nil), supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) _, err = userSvc.Create(context.Background(), &createUserCmd) require.Nil(t, err) @@ -384,7 +391,10 @@ func setupUpdateEmailTests(t *testing.T, cfg *setting.Cfg) (*user.User, *HTTPSer tempUserService := tempuserimpl.ProvideService(sqlStore, cfg) orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotatest.New(false, nil)) require.NoError(t, err) - userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, nil, nil, quotatest.New(false, nil), supportbundlestest.NewFakeBundleService()) + userSvc, err := userimpl.ProvideService( + sqlStore, orgSvc, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotatest.New(false, nil), supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) // Create test user @@ -610,7 +620,10 @@ func TestUser_UpdateEmail(t *testing.T) { tempUserSvc := tempuserimpl.ProvideService(sqlStore, settings) orgSvc, err := orgimpl.ProvideService(sqlStore, settings, quotatest.New(false, nil)) require.NoError(t, err) - userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, settings, nil, nil, quotatest.New(false, nil), supportbundlestest.NewFakeBundleService()) + userSvc, err := userimpl.ProvideService( + sqlStore, orgSvc, settings, nil, nil, tracing.InitializeTracerForTest(), + quotatest.New(false, nil), supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) server := SetupAPITestServer(t, func(hs *HTTPServer) { diff --git a/pkg/cmd/grafana-cli/commands/conflict_user_command.go b/pkg/cmd/grafana-cli/commands/conflict_user_command.go index faa8455867b..f9d592bb2b3 100644 --- a/pkg/cmd/grafana-cli/commands/conflict_user_command.go +++ b/pkg/cmd/grafana-cli/commands/conflict_user_command.go @@ -33,7 +33,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -func initConflictCfg(cmd *utils.ContextCommandLine) (*setting.Cfg, featuremgmt.FeatureToggles, error) { +func initConflictCfg(cmd *utils.ContextCommandLine) (*setting.Cfg, tracing.Tracer, featuremgmt.FeatureToggles, error) { configOptions := strings.Split(cmd.String("configOverrides"), " ") configOptions = append(configOptions, cmd.Args().Slice()...) cfg, err := setting.NewCfgFromArgs(setting.CommandLineArgs{ @@ -43,19 +43,33 @@ func initConflictCfg(cmd *utils.ContextCommandLine) (*setting.Cfg, featuremgmt.F }) if err != nil { - return nil, nil, err + return nil, nil, nil, err } features, err := featuremgmt.ProvideManagerService(cfg) - return cfg, features, err + if err != nil { + return nil, nil, nil, err + } + + tracingCfg, err := tracing.ProvideTracingConfig(cfg) + if err != nil { + return nil, nil, nil, fmt.Errorf("%v: %w", "failed to initialize tracer config", err) + } + + tracer, err := tracing.ProvideService(tracingCfg) + if err != nil { + return nil, nil, nil, fmt.Errorf("%v: %w", "failed to initialize tracer service", err) + } + + return cfg, tracer, features, err } func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx *cli.Context) (*ConflictResolver, error) { - cfg, features, err := initConflictCfg(cmd) + cfg, tracer, features, err := initConflictCfg(cmd) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to load configuration", err) } - s, err := getSqlStore(cfg, features) + s, err := getSqlStore(cfg, tracer, features) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to get to sql", err) } @@ -64,7 +78,7 @@ func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx return nil, fmt.Errorf("%v: %w", "failed to get users with conflicting logins", err) } quotaService := quotaimpl.ProvideService(s, cfg) - userService, err := userimpl.ProvideService(s, nil, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + userService, err := userimpl.ProvideService(s, nil, cfg, nil, nil, tracer, quotaService, supportbundlestest.NewFakeBundleService()) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to get user service", err) } @@ -78,16 +92,7 @@ func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx return &resolver, nil } -func getSqlStore(cfg *setting.Cfg, features featuremgmt.FeatureToggles) (*sqlstore.SQLStore, error) { - tracingCfg, err := tracing.ProvideTracingConfig(cfg) - if err != nil { - return nil, fmt.Errorf("%v: %w", "failed to initialize tracer config", err) - } - - tracer, err := tracing.ProvideService(tracingCfg) - if err != nil { - return nil, fmt.Errorf("%v: %w", "failed to initialize tracer service", err) - } +func getSqlStore(cfg *setting.Cfg, tracer tracing.Tracer, features featuremgmt.FeatureToggles) (*sqlstore.SQLStore, error) { bus := bus.ProvideBus(tracer) return sqlstore.ProvideService(cfg, features, &migrations.OSSMigrations{}, bus, tracer) } diff --git a/pkg/services/accesscontrol/database/database_test.go b/pkg/services/accesscontrol/database/database_test.go index 89b65d8cd8e..fda8acff224 100644 --- a/pkg/services/accesscontrol/database/database_test.go +++ b/pkg/services/accesscontrol/database/database_test.go @@ -91,9 +91,9 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) { } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - store, permissionStore, sql, teamSvc, _ := setupTestEnv(t) + store, permissionStore, usrSvc, teamSvc, _ := setupTestEnv(t) - user, team := createUserAndTeam(t, store.sql, sql, teamSvc, tt.orgID) + user, team := createUserAndTeam(t, store.sql, usrSvc, teamSvc, tt.orgID) for _, id := range tt.userPermissions { _, err := permissionStore.SetUserResourcePermission(context.Background(), tt.orgID, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{ @@ -164,8 +164,8 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) { func TestAccessControlStore_DeleteUserPermissions(t *testing.T) { t.Run("expect permissions in all orgs to be deleted", func(t *testing.T) { - store, permissionsStore, sql, teamSvc, _ := setupTestEnv(t) - user, _ := createUserAndTeam(t, store.sql, sql, teamSvc, 1) + store, permissionsStore, usrSvc, teamSvc, _ := setupTestEnv(t) + user, _ := createUserAndTeam(t, store.sql, usrSvc, teamSvc, 1) // generate permissions in org 1 _, err := permissionsStore.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{ @@ -204,8 +204,8 @@ func TestAccessControlStore_DeleteUserPermissions(t *testing.T) { }) t.Run("expect permissions in org 1 to be deleted", func(t *testing.T) { - store, permissionsStore, sql, teamSvc, _ := setupTestEnv(t) - user, _ := createUserAndTeam(t, store.sql, sql, teamSvc, 1) + store, permissionsStore, usrSvc, teamSvc, _ := setupTestEnv(t) + user, _ := createUserAndTeam(t, store.sql, usrSvc, teamSvc, 1) // generate permissions in org 1 _, err := permissionsStore.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: user.ID}, rs.SetResourcePermissionCommand{ @@ -246,8 +246,8 @@ func TestAccessControlStore_DeleteUserPermissions(t *testing.T) { func TestAccessControlStore_DeleteTeamPermissions(t *testing.T) { t.Run("expect permissions related to team to be deleted", func(t *testing.T) { - store, permissionsStore, sql, teamSvc, _ := setupTestEnv(t) - user, team := createUserAndTeam(t, store.sql, sql, teamSvc, 1) + store, permissionsStore, usrSvc, teamSvc, _ := setupTestEnv(t) + user, team := createUserAndTeam(t, store.sql, usrSvc, teamSvc, 1) // grant permission to the team _, err := permissionsStore.SetTeamResourcePermission(context.Background(), 1, team.ID, rs.SetResourcePermissionCommand{ @@ -280,8 +280,8 @@ func TestAccessControlStore_DeleteTeamPermissions(t *testing.T) { assert.Len(t, permissions, 0) }) t.Run("expect permissions not related to team to be kept", func(t *testing.T) { - store, permissionsStore, sql, teamSvc, _ := setupTestEnv(t) - user, team := createUserAndTeam(t, store.sql, sql, teamSvc, 1) + store, permissionsStore, usrSvc, teamSvc, _ := setupTestEnv(t) + user, team := createUserAndTeam(t, store.sql, usrSvc, teamSvc, 1) // grant permission to the team _, err := permissionsStore.SetTeamResourcePermission(context.Background(), 1, team.ID, rs.SetResourcePermissionCommand{ @@ -409,7 +409,10 @@ func setupTestEnv(t testing.TB) (*AccessControlStore, rs.Store, user.Service, te require.Equal(t, int64(1), orgID) require.NoError(t, err) - userService, err := userimpl.ProvideService(sql, orgService, cfg, teamService, localcache.ProvideService(), quotatest.New(false, nil), supportbundlestest.NewFakeBundleService()) + userService, err := userimpl.ProvideService( + sql, orgService, cfg, teamService, localcache.ProvideService(), tracing.InitializeTracerForTest(), + quotatest.New(false, nil), supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) return acstore, permissionStore, userService, teamService, orgService } diff --git a/pkg/services/accesscontrol/resourcepermissions/api_test.go b/pkg/services/accesscontrol/resourcepermissions/api_test.go index 57d55ffdd83..42732a3388a 100644 --- a/pkg/services/accesscontrol/resourcepermissions/api_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/api_test.go @@ -13,19 +13,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" - "github.com/grafana/grafana/pkg/services/org/orgimpl" - "github.com/grafana/grafana/pkg/services/quota/quotatest" - "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" - "github.com/grafana/grafana/pkg/services/team/teamimpl" + "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/services/user/userimpl" - "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" ) @@ -117,7 +110,7 @@ func TestApi_getDescription(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, _, _, _ := setupTestEnvironment(t, tt.options) + service, _, _ := setupTestEnvironment(t, tt.options) server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/access-control/%s/description", tt.options.Resource), nil) @@ -164,10 +157,10 @@ func TestApi_getPermissions(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, sql, cfg, _ := setupTestEnvironment(t, testOptions) + service, usrSvc, teamSvc := setupTestEnvironment(t, testOptions) server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) - seedPermissions(t, tt.resourceID, sql, cfg, service) + seedPermissions(t, tt.resourceID, usrSvc, teamSvc, service) permissions, recorder := getPermission(t, server, testOptions.Resource, tt.resourceID) assert.Equal(t, tt.expectedStatus, recorder.Code) @@ -241,7 +234,7 @@ func TestApi_setBuiltinRolePermission(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, _, _, _ := setupTestEnvironment(t, testOptions) + service, _, _ := setupTestEnvironment(t, testOptions) server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) recorder := setPermission(t, server, testOptions.Resource, tt.resourceID, tt.permission, "builtInRoles", tt.builtInRole) @@ -319,7 +312,7 @@ func TestApi_setTeamPermission(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, _, _, teamSvc := setupTestEnvironment(t, testOptions) + service, _, teamSvc := setupTestEnvironment(t, testOptions) server := setupTestServer(t, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}}, service) // seed team @@ -402,18 +395,13 @@ func TestApi_setUserPermission(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, sql, cfg, _ := setupTestEnvironment(t, testOptions) + service, usrSvc, _ := setupTestEnvironment(t, testOptions) server := setupTestServer(t, &user.SignedInUser{ OrgID: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}, }, service) - // seed user - orgSvc, err := orgimpl.ProvideService(sql, cfg, quotatest.New(false, nil)) - require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sql, orgSvc, cfg, nil, nil, "atest.FakeQuotaService{}, supportbundlestest.NewFakeBundleService()) - require.NoError(t, err) - _, err = usrSvc.Create(context.Background(), &user.CreateUserCommand{Login: "test", OrgID: 1}) + _, err := usrSvc.Create(context.Background(), &user.CreateUserCommand{Login: "test", OrgID: 1}) require.NoError(t, err) recorder := setPermission(t, server, testOptions.Resource, tt.resourceID, tt.permission, "users", strconv.Itoa(int(tt.userID))) @@ -507,20 +495,15 @@ func checkSeededPermissions(t *testing.T, permissions []resourcePermissionDTO) { } } -func seedPermissions(t *testing.T, resourceID string, sql db.DB, cfg *setting.Cfg, service *Service) { +func seedPermissions(t *testing.T, resourceID string, usrSvc user.Service, teamSvc team.Service, service *Service) { t.Helper() + // seed team 1 with "Edit" permission on dashboard 1 - teamSvc, err := teamimpl.ProvideService(sql, cfg, tracing.InitializeTracerForTest()) - require.NoError(t, err) team, err := teamSvc.CreateTeam(context.Background(), "test", "test@test.com", 1) require.NoError(t, err) _, err = service.SetTeamPermission(context.Background(), team.OrgID, team.ID, resourceID, "Edit") require.NoError(t, err) // seed user 1 with "View" permission on dashboard 1 - orgSvc, err := orgimpl.ProvideService(sql, cfg, quotatest.New(false, nil)) - require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sql, orgSvc, cfg, nil, nil, "atest.FakeQuotaService{}, supportbundlestest.NewFakeBundleService()) - require.NoError(t, err) u, err := usrSvc.Create(context.Background(), &user.CreateUserCommand{Login: "test", OrgID: 1}) require.NoError(t, err) _, err = service.SetUserPermission(context.Background(), u.OrgID, accesscontrol.User{ID: u.ID}, resourceID, "View") diff --git a/pkg/services/accesscontrol/resourcepermissions/service_test.go b/pkg/services/accesscontrol/resourcepermissions/service_test.go index e3b152e65c7..44bc6a98514 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/service_test.go @@ -44,17 +44,13 @@ func TestService_SetUserPermission(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, sql, cfg, _ := setupTestEnvironment(t, Options{ + service, usrSvc, _ := setupTestEnvironment(t, Options{ Resource: "dashboards", Assignments: Assignments{Users: true}, PermissionsToActions: nil, }) // seed user - orgSvc, err := orgimpl.ProvideService(sql, cfg, quotatest.New(false, nil)) - require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sql, orgSvc, cfg, nil, nil, "atest.FakeQuotaService{}, supportbundlestest.NewFakeBundleService()) - require.NoError(t, err) user, err := usrSvc.Create(context.Background(), &user.CreateUserCommand{Login: "test", OrgID: 1}) require.NoError(t, err) @@ -92,7 +88,7 @@ func TestService_SetTeamPermission(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, _, _, teamSvc := setupTestEnvironment(t, Options{ + service, _, teamSvc := setupTestEnvironment(t, Options{ Resource: "dashboards", Assignments: Assignments{Teams: true}, PermissionsToActions: nil, @@ -136,7 +132,7 @@ func TestService_SetBuiltInRolePermission(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, _, _, _ := setupTestEnvironment(t, Options{ + service, _, _ := setupTestEnvironment(t, Options{ Resource: "dashboards", Assignments: Assignments{BuiltInRoles: true}, PermissionsToActions: nil, @@ -209,14 +205,10 @@ func TestService_SetPermissions(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - service, sql, cfg, teamSvc := setupTestEnvironment(t, tt.options) + service, usrSvc, teamSvc := setupTestEnvironment(t, tt.options) // seed user - orgSvc, err := orgimpl.ProvideService(sql, cfg, quotatest.New(false, nil)) - require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sql, orgSvc, cfg, nil, nil, "atest.FakeQuotaService{}, supportbundlestest.NewFakeBundleService()) - require.NoError(t, err) - _, err = usrSvc.Create(context.Background(), &user.CreateUserCommand{Login: "user", OrgID: 1}) + _, err := usrSvc.Create(context.Background(), &user.CreateUserCommand{Login: "user", OrgID: 1}) require.NoError(t, err) _, err = teamSvc.CreateTeam(context.Background(), "team", "", 1) require.NoError(t, err) @@ -232,15 +224,25 @@ func TestService_SetPermissions(t *testing.T) { } } -func setupTestEnvironment(t *testing.T, ops Options) (*Service, db.DB, *setting.Cfg, team.Service) { +func setupTestEnvironment(t *testing.T, ops Options) (*Service, user.Service, team.Service) { t.Helper() sql := db.InitTestDB(t) cfg := setting.NewCfg() - teamSvc, err := teamimpl.ProvideService(sql, cfg, tracing.InitializeTracerForTest()) + tracer := tracing.InitializeTracerForTest() + + teamSvc, err := teamimpl.ProvideService(sql, cfg, tracer) require.NoError(t, err) - userSvc, err := userimpl.ProvideService(sql, nil, cfg, teamSvc, nil, quotatest.New(false, nil), supportbundlestest.NewFakeBundleService()) + + orgSvc, err := orgimpl.ProvideService(sql, cfg, quotatest.New(false, nil)) require.NoError(t, err) + + userSvc, err := userimpl.ProvideService( + sql, orgSvc, cfg, teamSvc, nil, tracer, + quotatest.New(false, nil), supportbundlestest.NewFakeBundleService(), + ) + require.NoError(t, err) + license := licensingtest.NewFakeLicensing() license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe() ac := acimpl.ProvideAccessControl(cfg) @@ -251,5 +253,5 @@ func setupTestEnvironment(t *testing.T, ops Options) (*Service, db.DB, *setting. ) require.NoError(t, err) - return service, sql, cfg, teamSvc + return service, userSvc, teamSvc } diff --git a/pkg/services/accesscontrol/resourcepermissions/store_bench_test.go b/pkg/services/accesscontrol/resourcepermissions/store_bench_test.go index 5134bc871fd..51a63a5deba 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store_bench_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/store_bench_test.go @@ -147,7 +147,9 @@ func generateTeamsAndUsers(b *testing.B, store db.DB, cfg *setting.Cfg, users in qs := quotatest.New(false, nil) orgSvc, err := orgimpl.ProvideService(store, cfg, qs) require.NoError(b, err) - usrSvc, err := userimpl.ProvideService(store, orgSvc, cfg, nil, nil, qs, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + store, orgSvc, cfg, nil, nil, tracing.InitializeTracerForTest(), + qs, supportbundlestest.NewFakeBundleService()) require.NoError(b, err) userIds := make([]int64, 0) teamIds := make([]int64, 0) diff --git a/pkg/services/accesscontrol/resourcepermissions/store_test.go b/pkg/services/accesscontrol/resourcepermissions/store_test.go index fc6647de5aa..0c92a48162e 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/store_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -527,7 +528,10 @@ func seedResourcePermissions( orgID, err := orgService.GetOrCreate(context.Background(), "test") require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sql, orgService, cfg, nil, nil, quotatest.New(false, nil), supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + sql, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotatest.New(false, nil), supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) create := func(login string, isServiceAccount bool) { diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index b640c050842..76694da9d26 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -24,7 +24,6 @@ import ( "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" - "github.com/grafana/grafana/pkg/services/quota/quotaimpl" "github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" @@ -252,6 +251,11 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { setup := func() { sqlStore, cfg = db.InitTestDBWithCfg(t) + cfg.AutoAssignOrg = true + cfg.AutoAssignOrgId = 1 + cfg.AutoAssignOrgRole = string(org.RoleViewer) + + tracer := tracing.InitializeTracerForTest() quotaService := quotatest.New(false, nil) // enable nested folders so that the folder table is populated for all the tests @@ -261,18 +265,21 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { dashboardWriteStore, err := ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) - usr := createUser(t, sqlStore, cfg, "viewer", "Viewer", false) + orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) + require.NoError(t, err) + usrSvc, err := userimpl.ProvideService( + sqlStore, orgService, cfg, nil, nil, tracer, + quotaService, supportbundlestest.NewFakeBundleService(), + ) + require.NoError(t, err) + + usr := createUser(t, usrSvc, orgService, "viewer", false) viewer = &user.SignedInUser{ UserID: usr.ID, OrgID: usr.OrgID, OrgRole: org.RoleViewer, } - orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) - require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sqlStore, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) - require.NoError(t, err) - // create admin user in the same org currentUserCmd := user.CreateUserCommand{Login: "admin", Email: "admin@test.com", Name: "an admin", IsAdmin: false, OrgID: viewer.OrgID} u, err := usrSvc.Create(context.Background(), ¤tUserCmd) @@ -298,7 +305,7 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { guardian.New = origNewGuardian }) - folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore), sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) + folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracer), cfg, dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore), sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) parentUID := "" for i := 0; ; i++ { @@ -439,27 +446,14 @@ func moveDashboard(t *testing.T, dashboardStore dashboards.Store, orgId int64, d return dash } -func createUser(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, name string, role string, isAdmin bool) user.User { +func createUser(t *testing.T, userSrv user.Service, orgSrv org.Service, name string, isAdmin bool) user.User { t.Helper() - cfg.AutoAssignOrg = true - cfg.AutoAssignOrgId = 1 - cfg.AutoAssignOrgRole = role - qs := quotaimpl.ProvideService(sqlStore, cfg) - orgService, err := orgimpl.ProvideService(sqlStore, cfg, qs) - require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sqlStore, orgService, cfg, nil, nil, qs, supportbundlestest.NewFakeBundleService()) - require.NoError(t, err) - - o, err := orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: fmt.Sprintf("test org %d", time.Now().UnixNano())}) + o, err := orgSrv.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: fmt.Sprintf("test org %d", time.Now().UnixNano())}) require.NoError(t, err) currentUserCmd := user.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin, OrgID: o.ID} - currentUser, err := usrSvc.Create(context.Background(), ¤tUserCmd) + currentUser, err := userSrv.Create(context.Background(), ¤tUserCmd) require.NoError(t, err) - orgs, err := orgService.GetUserOrgList(context.Background(), &org.GetUserOrgListQuery{UserID: currentUser.ID}) - require.NoError(t, err) - require.Equal(t, org.RoleType(role), orgs[0].Role) - require.Equal(t, o.ID, orgs[0].OrgID) return *currentUser } diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index b7fec91dac9..7dda316dc36 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -439,6 +439,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo webCtx := web.Context{Req: req} features := featuremgmt.WithFeatures() + tracer := tracing.InitializeTracerForTest() sqlStore, cfg := db.InitTestDBWithCfg(t) quotaService := quotatest.New(false, nil) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore), quotaService) @@ -460,7 +461,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo Cfg: cfg, features: featuremgmt.WithFeatures(), SQLStore: sqlStore, - folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil), + folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracer), cfg, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil), } // deliberate difference between signed in user and user in db to make it crystal clear @@ -473,7 +474,10 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo } orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + sqlStore, orgSvc, cfg, nil, nil, tracer, + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) _, err = usrSvc.Create(context.Background(), &cmd) require.NoError(t, err) diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 2ff4d3f84f3..771f673b078 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -872,7 +872,10 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo ctx := appcontext.WithUser(context.Background(), usr) orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + sqlStore, orgSvc, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) _, err = usrSvc.Create(context.Background(), &cmd) require.NoError(t, err) diff --git a/pkg/services/org/orgimpl/store_test.go b/pkg/services/org/orgimpl/store_test.go index 37c9f580d21..e946e97ca3d 100644 --- a/pkg/services/org/orgimpl/store_test.go +++ b/pkg/services/org/orgimpl/store_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/org" @@ -908,7 +909,10 @@ func createOrgAndUserSvc(t *testing.T, store db.DB, cfg *setting.Cfg) (org.Servi quotaService := quotaimpl.ProvideService(store, cfg) orgService, err := ProvideService(store, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(store, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + store, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) return orgService, usrSvc diff --git a/pkg/services/queryhistory/queryhistory_test.go b/pkg/services/queryhistory/queryhistory_test.go index 2b6a1cee6ef..5e7b8cad7d8 100644 --- a/pkg/services/queryhistory/queryhistory_test.go +++ b/pkg/services/queryhistory/queryhistory_test.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" @@ -65,7 +66,10 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo quotaService := quotatest.New(false, nil) orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + sqlStore, orgSvc, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) usr := user.SignedInUser{ diff --git a/pkg/services/quota/quotaimpl/quota_test.go b/pkg/services/quota/quotaimpl/quota_test.go index 3d127cb6d58..aa13fa80b03 100644 --- a/pkg/services/quota/quotaimpl/quota_test.go +++ b/pkg/services/quota/quotaimpl/quota_test.go @@ -94,7 +94,10 @@ func TestIntegrationQuotaCommandsAndQueries(t *testing.T) { quotaService := ProvideService(sqlStore, cfg) orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - userService, err := userimpl.ProvideService(sqlStore, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + userService, err := userimpl.ProvideService( + sqlStore, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) setupEnv(t, sqlStore, cfg, b, quotaService) diff --git a/pkg/services/serviceaccounts/database/store_test.go b/pkg/services/serviceaccounts/database/store_test.go index dfd9b688edb..5804a3fe8e6 100644 --- a/pkg/services/serviceaccounts/database/store_test.go +++ b/pkg/services/serviceaccounts/database/store_test.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/kvstore" + "github.com/grafana/grafana/pkg/infra/tracing" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apikey/apikeyimpl" "github.com/grafana/grafana/pkg/services/org" @@ -228,7 +229,10 @@ func setupTestDatabase(t *testing.T) (db.DB, *ServiceAccountsStoreImpl) { kvStore := kvstore.ProvideService(db) orgService, err := orgimpl.ProvideService(db, cfg, quotaService) require.NoError(t, err) - userSvc, err := userimpl.ProvideService(db, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + userSvc, err := userimpl.ProvideService( + db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) return db, ProvideServiceAccountsStore(cfg, db, apiKeyService, kvStore, userSvc, orgService) } diff --git a/pkg/services/serviceaccounts/tests/common.go b/pkg/services/serviceaccounts/tests/common.go index dce8daeffe2..66d15e35b9d 100644 --- a/pkg/services/serviceaccounts/tests/common.go +++ b/pkg/services/serviceaccounts/tests/common.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/apikey/apikeyimpl" "github.com/grafana/grafana/pkg/services/org" @@ -44,7 +45,10 @@ func SetupUserServiceAccount(t *testing.T, db db.DB, cfg *setting.Cfg, testUser quotaService := quotaimpl.ProvideService(db, cfg) orgService, err := orgimpl.ProvideService(db, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(db, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) org, err := orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{ @@ -111,7 +115,10 @@ func SetupUsersServiceAccounts(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, t quotaService := quotaimpl.ProvideService(sqlStore, cfg) orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(sqlStore, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + sqlStore, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) org, err := orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{ diff --git a/pkg/services/stats/statsimpl/stats_test.go b/pkg/services/stats/statsimpl/stats_test.go index f686c6a4251..e63d51fddc0 100644 --- a/pkg/services/stats/statsimpl/stats_test.go +++ b/pkg/services/stats/statsimpl/stats_test.go @@ -87,7 +87,10 @@ func populateDB(t *testing.T, db db.DB, cfg *setting.Cfg) { t.Helper() orgService, _ := orgimpl.ProvideService(db, cfg, quotatest.New(false, nil)) - userSvc, _ := userimpl.ProvideService(db, orgService, cfg, nil, nil, "atest.FakeQuotaService{}, supportbundlestest.NewFakeBundleService()) + userSvc, _ := userimpl.ProvideService( + db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + "atest.FakeQuotaService{}, supportbundlestest.NewFakeBundleService(), + ) bus := bus.ProvideBus(tracing.InitializeTracerForTest()) correlationsSvc := correlationstest.New(db, cfg, bus) diff --git a/pkg/services/team/teamimpl/store_test.go b/pkg/services/team/teamimpl/store_test.go index 62126495417..4fdeaab2ba4 100644 --- a/pkg/services/team/teamimpl/store_test.go +++ b/pkg/services/team/teamimpl/store_test.go @@ -51,8 +51,10 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { quotaService := quotaimpl.ProvideService(sqlStore, cfg) orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, teamSvc, nil, quotaService, - supportbundlestest.NewFakeBundleService()) + userSvc, err := userimpl.ProvideService( + sqlStore, orgSvc, cfg, teamSvc, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) t.Run("Given saved users and two teams", func(t *testing.T) { @@ -436,7 +438,10 @@ func TestIntegrationTeamCommandsAndQueries(t *testing.T) { quotaService := quotaimpl.ProvideService(sqlStore, cfg) orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotaService) require.NoError(t, err) - userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, teamSvc, nil, quotaService, supportbundlestest.NewFakeBundleService()) + userSvc, err := userimpl.ProvideService( + sqlStore, orgSvc, cfg, teamSvc, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) setup() userCmd = user.CreateUserCommand{ @@ -571,7 +576,10 @@ func TestIntegrationSQLStore_GetTeamMembers_ACFilter(t *testing.T) { quotaService := quotaimpl.ProvideService(store, cfg) orgSvc, err := orgimpl.ProvideService(store, cfg, quotaService) require.NoError(t, err) - userSvc, err := userimpl.ProvideService(store, orgSvc, cfg, teamSvc, nil, quotaService, supportbundlestest.NewFakeBundleService()) + userSvc, err := userimpl.ProvideService( + store, orgSvc, cfg, teamSvc, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) for i := 0; i < 4; i++ { diff --git a/pkg/services/user/userimpl/store_test.go b/pkg/services/user/userimpl/store_test.go index 5f768320051..45ed734c2f7 100644 --- a/pkg/services/user/userimpl/store_test.go +++ b/pkg/services/user/userimpl/store_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" @@ -37,7 +38,10 @@ func TestIntegrationUserDataAccess(t *testing.T) { orgService, err := orgimpl.ProvideService(ss, cfg, quotaService) require.NoError(t, err) userStore := ProvideStore(ss, setting.NewCfg()) - usrSvc, err := ProvideService(ss, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := ProvideService( + ss, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) usr := &user.SignedInUser{ OrgID: 1, @@ -554,7 +558,10 @@ func TestIntegrationUserDataAccess(t *testing.T) { ss := db.InitTestDB(t) orgService, err := orgimpl.ProvideService(ss, cfg, quotaService) require.NoError(t, err) - usrSvc, err := ProvideService(ss, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := ProvideService( + ss, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand { @@ -958,7 +965,10 @@ func createOrgAndUserSvc(t *testing.T, store db.DB, cfg *setting.Cfg) (org.Servi quotaService := quotaimpl.ProvideService(store, cfg) orgService, err := orgimpl.ProvideService(store, cfg, quotaService) require.NoError(t, err) - usrSvc, err := ProvideService(store, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := ProvideService( + store, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) return orgService, usrSvc diff --git a/pkg/services/user/userimpl/user.go b/pkg/services/user/userimpl/user.go index 8c885e4695e..c272934fa85 100644 --- a/pkg/services/user/userimpl/user.go +++ b/pkg/services/user/userimpl/user.go @@ -7,8 +7,12 @@ import ( "strings" "time" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/localcache" + "github.com/grafana/grafana/pkg/infra/tracing" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/quota" @@ -27,6 +31,7 @@ type Service struct { teamService team.Service cacheService *localcache.CacheService cfg *setting.Cfg + tracer tracing.Tracer } func ProvideService( @@ -34,9 +39,8 @@ func ProvideService( orgService org.Service, cfg *setting.Cfg, teamService team.Service, - cacheService *localcache.CacheService, - quotaService quota.Service, - bundleRegistry supportbundles.Service, + cacheService *localcache.CacheService, tracer tracing.Tracer, + quotaService quota.Service, bundleRegistry supportbundles.Service, ) (user.Service, error) { store := ProvideStore(db, cfg) s := &Service{ @@ -45,6 +49,7 @@ func ProvideService( cfg: cfg, teamService: teamService, cacheService: cacheService, + tracer: tracer, } defaultLimits, err := readQuotaConfig(cfg) @@ -55,7 +60,7 @@ func ProvideService( if err := quotaService.RegisterQuotaReporter("a.NewUsageReporter{ TargetSrv: quota.TargetSrv(user.QuotaTargetSrv), DefaultLimits: defaultLimits, - Reporter: s.Usage, + Reporter: s.usage, }); err != nil { return s, err } @@ -87,21 +92,10 @@ func (s *Service) GetUsageStats(ctx context.Context) map[string]any { return stats } -func (s *Service) Usage(ctx context.Context, _ *quota.ScopeParameters) (*quota.Map, error) { - u := "a.Map{} - if used, err := s.store.Count(ctx); err != nil { - return u, err - } else { - tag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope) - if err != nil { - return u, err - } - u.Set(tag, used) - } - return u, nil -} - func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*user.User, error) { + ctx, span := s.tracer.Start(ctx, "user.Create") + defer span.End() + if len(cmd.Login) == 0 { cmd.Login = cmd.Email } @@ -126,8 +120,7 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use cmd.Email = cmd.Login } - err = s.store.LoginConflict(ctx, cmd.Login, cmd.Email) - if err != nil { + if err := s.store.LoginConflict(ctx, cmd.Login, cmd.Email); err != nil { return nil, user.ErrUserAlreadyExists } @@ -202,27 +195,48 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use } func (s *Service) Delete(ctx context.Context, cmd *user.DeleteUserCommand) error { + ctx, span := s.tracer.Start(ctx, "user.Delete", trace.WithAttributes( + attribute.Int64("userID", cmd.UserID), + )) + defer span.End() + _, err := s.store.GetByID(ctx, cmd.UserID) if err != nil { return err } - // delete from all the stores + return s.store.Delete(ctx, cmd.UserID) } func (s *Service) GetByID(ctx context.Context, query *user.GetUserByIDQuery) (*user.User, error) { + ctx, span := s.tracer.Start(ctx, "user.GetByID", trace.WithAttributes( + attribute.Int64("userID", query.ID), + )) + defer span.End() + return s.store.GetByID(ctx, query.ID) } func (s *Service) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) { + ctx, span := s.tracer.Start(ctx, "user.GetByLogin") + defer span.End() + return s.store.GetByLogin(ctx, query) } func (s *Service) GetByEmail(ctx context.Context, query *user.GetUserByEmailQuery) (*user.User, error) { + ctx, span := s.tracer.Start(ctx, "user.GetByEmail") + defer span.End() + return s.store.GetByEmail(ctx, query) } func (s *Service) Update(ctx context.Context, cmd *user.UpdateUserCommand) error { + ctx, span := s.tracer.Start(ctx, "user.Update", trace.WithAttributes( + attribute.Int64("userID", cmd.UserID), + )) + defer span.End() + usr, err := s.store.GetByID(ctx, cmd.UserID) if err != nil { return err @@ -273,6 +287,11 @@ func (s *Service) Update(ctx context.Context, cmd *user.UpdateUserCommand) error } func (s *Service) UpdateLastSeenAt(ctx context.Context, cmd *user.UpdateUserLastSeenAtCommand) error { + ctx, span := s.tracer.Start(ctx, "user.UpdateLastSeen", trace.WithAttributes( + attribute.Int64("userID", cmd.UserID), + )) + defer span.End() + u, err := s.GetSignedInUserWithCacheCtx(ctx, &user.GetSignedInUserQuery{ UserID: cmd.UserID, OrgID: cmd.OrgID, @@ -294,6 +313,12 @@ func shouldUpdateLastSeen(t time.Time) bool { } func (s *Service) GetSignedInUserWithCacheCtx(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) { + ctx, span := s.tracer.Start(ctx, "user.GetSignedInUserWithCacheCtx", trace.WithAttributes( + attribute.Int64("userID", query.UserID), + attribute.Int64("orgID", query.OrgID), + )) + defer span.End() + var signedInUser *user.SignedInUser // only check cache if we have a user ID and an org ID in query @@ -321,54 +346,62 @@ func newSignedInUserCacheKey(orgID, userID int64) string { } func (s *Service) GetSignedInUser(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) { - signedInUser, err := s.store.GetSignedInUser(ctx, query) + ctx, span := s.tracer.Start(ctx, "user.GetSignedInUser", trace.WithAttributes( + attribute.Int64("userID", query.UserID), + attribute.Int64("orgID", query.OrgID), + )) + defer span.End() + + usr, err := s.store.GetSignedInUser(ctx, query) if err != nil { return nil, err } - getTeamsByUserQuery := &team.GetTeamIDsByUserQuery{ - OrgID: signedInUser.OrgID, - UserID: signedInUser.UserID, - } - signedInUser.Teams, err = s.teamService.GetTeamIDsByUser(ctx, getTeamsByUserQuery) + usr.Teams, err = s.teamService.GetTeamIDsByUser(ctx, &team.GetTeamIDsByUserQuery{ + OrgID: usr.OrgID, + UserID: usr.UserID, + }) if err != nil { return nil, err } - return signedInUser, err + return usr, err } func (s *Service) Search(ctx context.Context, query *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) { + ctx, span := s.tracer.Start(ctx, "user.Search", trace.WithAttributes( + attribute.Int64("orgID", query.OrgID), + )) + defer span.End() + return s.store.Search(ctx, query) } func (s *Service) BatchDisableUsers(ctx context.Context, cmd *user.BatchDisableUsersCommand) error { + ctx, span := s.tracer.Start(ctx, "user.BatchDisableUsers", trace.WithAttributes( + attribute.Int64Slice("userIDs", cmd.UserIDs), + )) + defer span.End() + return s.store.BatchDisableUsers(ctx, cmd) } func (s *Service) GetProfile(ctx context.Context, query *user.GetUserProfileQuery) (*user.UserProfileDTO, error) { - result, err := s.store.GetProfile(ctx, query) - return result, err -} + ctx, span := s.tracer.Start(ctx, "user.GetProfile", trace.WithAttributes( + attribute.Int64("userID", query.UserID), + )) + defer span.End() -func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) { - limits := "a.Map{} - - if cfg == nil { - return limits, nil - } - - globalQuotaTag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope) - if err != nil { - return limits, err - } - - limits.Set(globalQuotaTag, cfg.Quota.Global.User) - return limits, nil + return s.store.GetProfile(ctx, query) } // CreateServiceAccount creates a service account in the user table and adds service account to an organisation in the org_user table func (s *Service) CreateServiceAccount(ctx context.Context, cmd *user.CreateUserCommand) (*user.User, error) { + ctx, span := s.tracer.Start(ctx, "user.CreateServiceAccount", trace.WithAttributes( + attribute.Int64("orgID", cmd.OrgID), + )) + defer span.End() + cmd.Email = cmd.Login err := s.store.LoginConflict(ctx, cmd.Login, cmd.Email) if err != nil { @@ -462,6 +495,36 @@ func (s *Service) supportBundleCollector() supportbundles.Collector { } } +func (s *Service) usage(ctx context.Context, _ *quota.ScopeParameters) (*quota.Map, error) { + u := "a.Map{} + if used, err := s.store.Count(ctx); err != nil { + return u, err + } else { + tag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope) + if err != nil { + return u, err + } + u.Set(tag, used) + } + return u, nil +} + +func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) { + limits := "a.Map{} + + if cfg == nil { + return limits, nil + } + + globalQuotaTag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope) + if err != nil { + return limits, err + } + + limits.Set(globalQuotaTag, cfg.Quota.Global.User) + return limits, nil +} + // This is just to ensure that all users have a valid uid. // To protect against upgrade / downgrade we need to run this for a couple of releases. // FIXME: Remove this migration and make uid field required https://github.com/grafana/identity-access-team/issues/552 diff --git a/pkg/services/user/userimpl/user_test.go b/pkg/services/user/userimpl/user_test.go index 0b9a90b854b..6cc9512507c 100644 --- a/pkg/services/user/userimpl/user_test.go +++ b/pkg/services/user/userimpl/user_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/localcache" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/team/teamtest" @@ -25,6 +26,7 @@ func TestUserService(t *testing.T) { orgService: orgService, cacheService: localcache.ProvideService(), teamService: &teamtest.FakeService{}, + tracer: tracing.InitializeTracerForTest(), } userService.cfg = setting.NewCfg() @@ -100,6 +102,7 @@ func TestUserService(t *testing.T) { orgService: orgService, cacheService: localcache.ProvideService(), teamService: teamtest.NewFakeService(), + tracer: tracing.InitializeTracerForTest(), } usr := &user.SignedInUser{ OrgID: 1, @@ -149,7 +152,10 @@ func TestUserService(t *testing.T) { func TestService_Update(t *testing.T) { setup := func(opts ...func(svc *Service)) *Service { - service := &Service{store: &FakeUserStore{}} + service := &Service{ + store: &FakeUserStore{}, + tracer: tracing.InitializeTracerForTest(), + } for _, o := range opts { o(service) } @@ -204,6 +210,33 @@ func TestService_Update(t *testing.T) { }) } +func TestUpdateLastSeenAt(t *testing.T) { + userStore := newUserStoreFake() + orgService := orgtest.NewOrgServiceFake() + userService := Service{ + store: userStore, + orgService: orgService, + cacheService: localcache.ProvideService(), + teamService: &teamtest.FakeService{}, + tracer: tracing.InitializeTracerForTest(), + } + userService.cfg = setting.NewCfg() + + t.Run("update last seen at", func(t *testing.T) { + userStore.ExpectedSignedInUser = &user.SignedInUser{UserID: 1, OrgID: 1, Email: "email", Login: "login", Name: "name", LastSeenAt: time.Now().Add(-10 * time.Minute)} + err := userService.UpdateLastSeenAt(context.Background(), &user.UpdateUserLastSeenAtCommand{UserID: 1, OrgID: 1}) + require.NoError(t, err) + }) + + userService.cacheService.Flush() + + t.Run("do not update last seen at", func(t *testing.T) { + userStore.ExpectedSignedInUser = &user.SignedInUser{UserID: 1, OrgID: 1, Email: "email", Login: "login", Name: "name", LastSeenAt: time.Now().Add(-1 * time.Minute)} + err := userService.UpdateLastSeenAt(context.Background(), &user.UpdateUserLastSeenAtCommand{UserID: 1, OrgID: 1}) + require.ErrorIs(t, err, user.ErrLastSeenUpToDate, err) + }) +} + func TestMetrics(t *testing.T) { userStore := newUserStoreFake() orgService := orgtest.NewOrgServiceFake() @@ -213,6 +246,7 @@ func TestMetrics(t *testing.T) { orgService: orgService, cacheService: localcache.ProvideService(), teamService: &teamtest.FakeService{}, + tracer: tracing.InitializeTracerForTest(), } t.Run("update user with role None", func(t *testing.T) { @@ -303,29 +337,3 @@ func (f *FakeUserStore) Count(ctx context.Context) (int64, error) { func (f *FakeUserStore) CountUserAccountsWithEmptyRole(ctx context.Context) (int64, error) { return f.ExpectedCountUserAccountsWithEmptyRoles, nil } - -func TestUpdateLastSeenAt(t *testing.T) { - userStore := newUserStoreFake() - orgService := orgtest.NewOrgServiceFake() - userService := Service{ - store: userStore, - orgService: orgService, - cacheService: localcache.ProvideService(), - teamService: &teamtest.FakeService{}, - } - userService.cfg = setting.NewCfg() - - t.Run("update last seen at", func(t *testing.T) { - userStore.ExpectedSignedInUser = &user.SignedInUser{UserID: 1, OrgID: 1, Email: "email", Login: "login", Name: "name", LastSeenAt: time.Now().Add(-10 * time.Minute)} - err := userService.UpdateLastSeenAt(context.Background(), &user.UpdateUserLastSeenAtCommand{UserID: 1, OrgID: 1}) - require.NoError(t, err) - }) - - userService.cacheService.Flush() - - t.Run("do not update last seen at", func(t *testing.T) { - userStore.ExpectedSignedInUser = &user.SignedInUser{UserID: 1, OrgID: 1, Email: "email", Login: "login", Name: "name", LastSeenAt: time.Now().Add(-1 * time.Minute)} - err := userService.UpdateLastSeenAt(context.Background(), &user.UpdateUserLastSeenAtCommand{UserID: 1, OrgID: 1}) - require.ErrorIs(t, err, user.ErrLastSeenUpToDate, err) - }) -} diff --git a/pkg/tests/api/alerting/api_alertmanager_test.go b/pkg/tests/api/alerting/api_alertmanager_test.go index afb2e920897..653def960c5 100644 --- a/pkg/tests/api/alerting/api_alertmanager_test.go +++ b/pkg/tests/api/alerting/api_alertmanager_test.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ngstore "github.com/grafana/grafana/pkg/services/ngalert/store" @@ -2649,7 +2650,10 @@ func createUser(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCom quotaService := quotaimpl.ProvideService(db, cfg) orgService, err := orgimpl.ProvideService(db, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(db, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) u, err := usrSvc.Create(context.Background(), &cmd) diff --git a/pkg/tests/api/correlations/common_test.go b/pkg/tests/api/correlations/common_test.go index a42a497eda3..c14f4b3a937 100644 --- a/pkg/tests/api/correlations/common_test.go +++ b/pkg/tests/api/correlations/common_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/server" "github.com/grafana/grafana/pkg/services/correlations" "github.com/grafana/grafana/pkg/services/datasources" @@ -160,7 +161,10 @@ func (c TestContext) createUser(cmd user.CreateUserCommand) User { quotaService := quotaimpl.ProvideService(store, c.env.Cfg) orgService, err := orgimpl.ProvideService(store, c.env.Cfg, quotaService) require.NoError(c.t, err) - usrSvc, err := userimpl.ProvideService(store, orgService, c.env.Cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + store, orgService, c.env.Cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(c.t, err) user, err := usrSvc.Create(context.Background(), &cmd) diff --git a/pkg/tests/api/dashboards/api_dashboards_test.go b/pkg/tests/api/dashboards/api_dashboards_test.go index 0b280eed3d3..604f0df60b8 100644 --- a/pkg/tests/api/dashboards/api_dashboards_test.go +++ b/pkg/tests/api/dashboards/api_dashboards_test.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/dashboardimport" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" @@ -123,7 +124,10 @@ func createUser(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCom quotaService := quotaimpl.ProvideService(db, cfg) orgService, err := orgimpl.ProvideService(db, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(db, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) u, err := usrSvc.Create(context.Background(), &cmd) diff --git a/pkg/tests/api/folders/api_folder_test.go b/pkg/tests/api/folders/api_folder_test.go index 8c959967b31..c61e94b0cd9 100644 --- a/pkg/tests/api/folders/api_folder_test.go +++ b/pkg/tests/api/folders/api_folder_test.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana-openapi-client-go/client/folders" "github.com/grafana/grafana-openapi-client-go/models" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" @@ -215,7 +216,10 @@ func createUser(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCom quotaService := quotaimpl.ProvideService(db, cfg) orgService, err := orgimpl.ProvideService(db, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(db, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) u, err := usrSvc.Create(context.Background(), &cmd) diff --git a/pkg/tests/api/plugins/api_plugins_test.go b/pkg/tests/api/plugins/api_plugins_test.go index a026f9d8336..ba04251c1fb 100644 --- a/pkg/tests/api/plugins/api_plugins_test.go +++ b/pkg/tests/api/plugins/api_plugins_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/quota/quotaimpl" @@ -201,7 +202,10 @@ func createUser(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCom quotaService := quotaimpl.ProvideService(db, cfg) orgService, err := orgimpl.ProvideService(db, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(db, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) _, err = usrSvc.Create(context.Background(), &cmd) diff --git a/pkg/tests/api/stats/admin_test.go b/pkg/tests/api/stats/admin_test.go index 019fd8608bc..3e264a75e0c 100644 --- a/pkg/tests/api/stats/admin_test.go +++ b/pkg/tests/api/stats/admin_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/quota/quotaimpl" @@ -89,7 +90,10 @@ func createUser(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCom quotaService := quotaimpl.ProvideService(db, cfg) orgService, err := orgimpl.ProvideService(db, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(db, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) u, err := usrSvc.Create(context.Background(), &cmd) diff --git a/pkg/tests/apis/helper.go b/pkg/tests/apis/helper.go index 74afbfbe1f0..c491dea3847 100644 --- a/pkg/tests/apis/helper.go +++ b/pkg/tests/apis/helper.go @@ -397,8 +397,9 @@ func (c K8sTestHelper) createTestUsers(orgName string) OrgUsers { require.NoError(c.t, err) cache := localcache.ProvideService() - userSvc, err := userimpl.ProvideService(store, - orgService, c.env.Cfg, teamSvc, cache, quotaService, + userSvc, err := userimpl.ProvideService( + store, orgService, c.env.Cfg, teamSvc, + cache, tracing.InitializeTracerForTest(), quotaService, supportbundlestest.NewFakeBundleService()) require.NoError(c.t, err) diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index 24b046d7aed..c5c9b14452c 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/extensions" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/fs" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/server" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" @@ -442,7 +443,9 @@ func CreateUser(t *testing.T, store db.DB, cfg *setting.Cfg, cmd user.CreateUser quotaService := quotaimpl.ProvideService(store, cfg) orgService, err := orgimpl.ProvideService(store, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(store, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + store, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) o, err := orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: fmt.Sprintf("test org %d", time.Now().UnixNano())}) diff --git a/pkg/tests/utils.go b/pkg/tests/utils.go index 4f4d7fcc28a..f81683c80f4 100644 --- a/pkg/tests/utils.go +++ b/pkg/tests/utils.go @@ -10,6 +10,7 @@ import ( "github.com/go-openapi/strfmt" goapi "github.com/grafana/grafana-openapi-client-go/client" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" @@ -30,7 +31,10 @@ func CreateUser(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCom quotaService := quotaimpl.ProvideService(db, cfg) orgService, err := orgimpl.ProvideService(db, cfg, quotaService) require.NoError(t, err) - usrSvc, err := userimpl.ProvideService(db, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + usrSvc, err := userimpl.ProvideService( + db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(), + quotaService, supportbundlestest.NewFakeBundleService(), + ) require.NoError(t, err) u, err := usrSvc.Create(context.Background(), &cmd) From 125ac18fa386b5f72f20e1212bbedcbdb6521d36 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Tue, 30 Apr 2024 13:10:04 +0100 Subject: [PATCH 209/222] AzureMonitor: Enable session ID header for Log Analytics (#86320) * Enable option - Update sdk * Sync go.work --- go.mod | 2 +- go.sum | 3 +-- go.work.sum | 2 ++ pkg/tsdb/azuremonitor/httpclient.go | 2 ++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0eb181b83cf..c69f43eda6a 100644 --- a/go.mod +++ b/go.mod @@ -95,7 +95,7 @@ require ( github.com/grafana/dskit v0.0.0-20240104111617-ea101a3b86eb // @grafana/grafana-backend-group github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad github.com/grafana/grafana-aws-sdk v0.25.0 // @grafana/aws-datasources - github.com/grafana/grafana-azure-sdk-go/v2 v2.0.1 // @grafana/partner-datasources + github.com/grafana/grafana-azure-sdk-go/v2 v2.0.2 // @grafana/partner-datasources github.com/grafana/grafana-google-sdk-go v0.1.0 // @grafana/partner-datasources github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group github.com/grafana/grafana-plugin-sdk-go v0.227.0 // @grafana/plugins-platform-backend diff --git a/go.sum b/go.sum index 69a180ea839..a6ba6e09a17 100644 --- a/go.sum +++ b/go.sum @@ -2166,8 +2166,7 @@ github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 h1:jxJJ5z0GxqhWFbQU github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447/go.mod h1:IxsY6mns6Q5sAnWcrptrgUrSglTZJXH/kXr9nbpb/9I= github.com/grafana/grafana-aws-sdk v0.25.0 h1:XNi3iA/C/KPArmVbQfbwKQROaIotd38nCRjNE6P1UP0= github.com/grafana/grafana-aws-sdk v0.25.0/go.mod h1:3zghFF6edrxn0d6k6X9HpGZXDH+VfA+MwD2Pc/9X0ec= -github.com/grafana/grafana-azure-sdk-go/v2 v2.0.1 h1:a/zb8uX7EvmS2YAFbYPyGEnZP8jMp7WppAm05Qtunok= -github.com/grafana/grafana-azure-sdk-go/v2 v2.0.1/go.mod h1:nW7pr7POOGafhyOrq8V0ouXBcXTmpRCer3sDAfeSV+Y= +github.com/grafana/grafana-azure-sdk-go/v2 v2.0.2 h1:CWT7mOBPUht9n7F/NiBQnEM05pFmCP3Z8CZPGCVC1tM= github.com/grafana/grafana-google-sdk-go v0.1.0 h1:LKGY8z2DSxKjYfr2flZsWgTRTZ6HGQbTqewE3JvRaNA= github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs= diff --git a/go.work.sum b/go.work.sum index 51b558d5e4a..9bec9f3d401 100644 --- a/go.work.sum +++ b/go.work.sum @@ -647,6 +647,8 @@ github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b h1:Ha+kSIoTutf4ytlVw github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b/go.mod h1:3UsooRp7yW5/NJQBlXcTsAHOoykEhNUYXkQ3r6ehEEY= github.com/grafana/gomemcache v0.0.0-20231023152154-6947259a0586 h1:/of8Z8taCPftShATouOrBVy6GaTTjgQd/VfNiZp/VXQ= github.com/grafana/gomemcache v0.0.0-20231023152154-6947259a0586/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU= +github.com/grafana/grafana-azure-sdk-go/v2 v2.0.2 h1:CWT7mOBPUht9n7F/NiBQnEM05pFmCP3Z8CZPGCVC1tM= +github.com/grafana/grafana-azure-sdk-go/v2 v2.0.2/go.mod h1:s8GLONgVh/svnSsO0Eo+OgXc/RZqozI5/0n+pNm3MEE= github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJf9zRdVxWV/W4Wi+Ox6gifgs= github.com/grafana/grafana-plugin-sdk-go v0.215.0/go.mod h1:nBsh3jRItKQUXDF2BQkiQCPxqrsSQeb+7hiFyJTO1RE= github.com/grafana/grafana-plugin-sdk-go v0.216.0/go.mod h1:FdvSvOliqpVLnytM7e89zCFyYPDE6VOn9SIjVQRvVxM= diff --git a/pkg/tsdb/azuremonitor/httpclient.go b/pkg/tsdb/azuremonitor/httpclient.go index 53fd0735d7a..1b6338a75de 100644 --- a/pkg/tsdb/azuremonitor/httpclient.go +++ b/pkg/tsdb/azuremonitor/httpclient.go @@ -39,6 +39,8 @@ func newHTTPClient(ctx context.Context, route types.AzRoute, model types.Datasou authOpts := azhttpclient.NewAuthOptions(azureSettings) authOpts.AllowUserIdentity() + // Allows requests from the same identity but different Grafana users to be identified as such by the server + authOpts.AddRateLimitSession(true) authOpts.Scopes(route.Scopes) azhttpclient.AddAzureAuthentication(&clientOpts, authOpts, model.Credentials) } From 76d94b35c98dbe954033fa716a530997b1a52af8 Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Tue, 30 Apr 2024 15:10:27 +0300 Subject: [PATCH 210/222] SSO: fix settings merge for SAML fields (#86970) * fix sso settings merge for saml fields * change func name --- .../ssosettings/ssosettingsimpl/service.go | 18 +++- .../ssosettingsimpl/service_test.go | 85 +++++++++++++++---- 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/pkg/services/ssosettings/ssosettingsimpl/service.go b/pkg/services/ssosettings/ssosettingsimpl/service.go index f1f2f3e97bc..1bb029821ad 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service.go @@ -470,7 +470,9 @@ func mergeSettings(storedSettings, systemSettings map[string]any) map[string]any for k, v := range systemSettings { if _, ok := settings[k]; !ok { - settings[k] = v + if isMergingAllowed(k) { + settings[k] = v + } } else if isURL(k) && isEmptyString(settings[k]) { // Overwrite all URL settings from the DB containing an empty string with their value // from the system settings. This fixes an issue with empty auth_url, api_url and token_url @@ -483,6 +485,20 @@ func mergeSettings(storedSettings, systemSettings map[string]any) map[string]any return settings } +// isMergingAllowed returns true if the field provided can be merged from the system settings. +// It won't allow SAML fields that are part of a group of settings to be merged from system settings +// because the DB settings already contain one valid setting from each group. +func isMergingAllowed(fieldName string) bool { + forbiddenMergePatterns := []string{"certificate", "private_key", "idp_metadata"} + + for _, v := range forbiddenMergePatterns { + if strings.Contains(strings.ToLower(fieldName), strings.ToLower(v)) { + return false + } + } + return true +} + // mergeSecrets returns a new map with the current value for secrets that have not been updated func mergeSecrets(settings map[string]any, storedSettings map[string]any) (map[string]any, error) { settingsWithSecrets := map[string]any{} diff --git a/pkg/services/ssosettings/ssosettingsimpl/service_test.go b/pkg/services/ssosettings/ssosettingsimpl/service_test.go index 7a9bd0919ad..7f9cd0aba05 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service_test.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service_test.go @@ -40,13 +40,15 @@ func TestService_GetForProvider(t *testing.T) { t.Parallel() testCases := []struct { - name string - setup func(env testEnv) - want *models.SSOSettings - wantErr bool + name string + provider string + setup func(env testEnv) + want *models.SSOSettings + wantErr bool }{ { - name: "should return successfully", + name: "should return successfully", + provider: "github", setup: func(env testEnv) { env.store.ExpectedSSOSetting = &models.SSOSettings{ Provider: "github", @@ -72,13 +74,15 @@ func TestService_GetForProvider(t *testing.T) { wantErr: false, }, { - name: "should return error if store returns an error different than not found", - setup: func(env testEnv) { env.store.ExpectedError = fmt.Errorf("error") }, - want: nil, - wantErr: true, + name: "should return error if store returns an error different than not found", + provider: "github", + setup: func(env testEnv) { env.store.ExpectedError = fmt.Errorf("error") }, + want: nil, + wantErr: true, }, { - name: "should fallback to the system settings if store returns not found", + name: "should fallback to the system settings if store returns not found", + provider: "github", setup: func(env testEnv) { env.store.ExpectedError = ssosettings.ErrNotFound env.fallbackStrategy.ExpectedIsMatch = true @@ -99,7 +103,8 @@ func TestService_GetForProvider(t *testing.T) { wantErr: false, }, { - name: "should return error if the fallback strategy was not found", + name: "should return error if the fallback strategy was not found", + provider: "github", setup: func(env testEnv) { env.store.ExpectedError = ssosettings.ErrNotFound env.fallbackStrategy.ExpectedIsMatch = false @@ -108,7 +113,8 @@ func TestService_GetForProvider(t *testing.T) { wantErr: true, }, { - name: "should return error if fallback strategy returns error", + name: "should return error if fallback strategy returns error", + provider: "github", setup: func(env testEnv) { env.store.ExpectedError = ssosettings.ErrNotFound env.fallbackStrategy.ExpectedIsMatch = true @@ -118,7 +124,8 @@ func TestService_GetForProvider(t *testing.T) { wantErr: true, }, { - name: "should decrypt secrets if data is coming from store", + name: "should decrypt secrets if data is coming from store", + provider: "github", setup: func(env testEnv) { env.store.ExpectedSSOSetting = &models.SSOSettings{ Provider: "github", @@ -152,7 +159,8 @@ func TestService_GetForProvider(t *testing.T) { wantErr: false, }, { - name: "should not decrypt secrets if data is coming from the fallback strategy", + name: "should not decrypt secrets if data is coming from the fallback strategy", + provider: "github", setup: func(env testEnv) { env.store.ExpectedError = ssosettings.ErrNotFound env.fallbackStrategy.ExpectedIsMatch = true @@ -176,7 +184,8 @@ func TestService_GetForProvider(t *testing.T) { wantErr: false, }, { - name: "should return an error if the data in the store is invalid", + name: "should return an error if the data in the store is invalid", + provider: "github", setup: func(env testEnv) { env.store.ExpectedSSOSetting = &models.SSOSettings{ Provider: "github", @@ -196,7 +205,8 @@ func TestService_GetForProvider(t *testing.T) { wantErr: true, }, { - name: "correctly merge the DB and system settings", + name: "correctly merge URLs from the DB and system settings", + provider: "github", setup: func(env testEnv) { env.store.ExpectedSSOSetting = &models.SSOSettings{ Provider: "github", @@ -231,6 +241,45 @@ func TestService_GetForProvider(t *testing.T) { }, wantErr: false, }, + { + name: "correctly merge group of settings for SAML", + provider: "saml", + setup: func(env testEnv) { + env.store.ExpectedSSOSetting = &models.SSOSettings{ + Provider: "saml", + Settings: map[string]any{ + "certificate": base64.RawStdEncoding.EncodeToString([]byte("valid-certificate")), + "private_key_path": base64.RawStdEncoding.EncodeToString([]byte("path/to/private/key")), + "idp_metadata_url": "https://idp-metadata.com", + }, + Source: models.DB, + } + env.fallbackStrategy.ExpectedIsMatch = true + env.fallbackStrategy.ExpectedConfigs = map[string]map[string]any{ + "saml": { + "name": "test-settings", + "certificate_path": "path/to/certificate", + "private_key": "this-is-a-valid-private-key", + "idp_metadata_path": "path/to/metadata", + "max_issue_delay": "1h", + }, + } + env.secrets.On("Decrypt", mock.Anything, []byte("valid-certificate"), mock.Anything).Return([]byte("decrypted-valid-certificate"), nil).Once() + env.secrets.On("Decrypt", mock.Anything, []byte("path/to/private/key"), mock.Anything).Return([]byte("decrypted/path/to/private/key"), nil).Once() + }, + want: &models.SSOSettings{ + Provider: "saml", + Settings: map[string]any{ + "name": "test-settings", + "certificate": "decrypted-valid-certificate", + "private_key_path": "decrypted/path/to/private/key", + "idp_metadata_url": "https://idp-metadata.com", + "max_issue_delay": "1h", + }, + Source: models.DB, + }, + wantErr: false, + }, } for _, tc := range testCases { @@ -241,12 +290,12 @@ func TestService_GetForProvider(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - env := setupTestEnv(t, false, false, false) + env := setupTestEnv(t, true, false, true) if tc.setup != nil { tc.setup(env) } - actual, err := env.service.GetForProvider(context.Background(), "github") + actual, err := env.service.GetForProvider(context.Background(), tc.provider) if tc.wantErr { require.Error(t, err) From 2fc99375dffb31132ce6dffab4a9aff6aabd1d9c Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 30 Apr 2024 15:12:45 +0200 Subject: [PATCH 211/222] Chore: Upgrade otel dependencies (#86994) * Chore: Upgrade otel dependencies * sdk changes * ignore deprecated go.opentelemetry.io/otel/exporters/jaeger for now * use latest commit from sdk branch * sdk v0.228.0 --- .golangci.toml | 7 ++ go.mod | 41 ++++++------ go.sum | 82 +++++++++++------------ go.work.sum | 152 ++++++++++++++++++++++++++++++++++++++++++- pkg/apiserver/go.mod | 36 +++++----- pkg/apiserver/go.sum | 52 +++++---------- pkg/promlib/go.mod | 34 +++++----- pkg/promlib/go.sum | 53 +++++---------- 8 files changed, 291 insertions(+), 166 deletions(-) diff --git a/.golangci.toml b/.golangci.toml index fd4fb00286f..acc7e3eaede 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -182,6 +182,13 @@ text = "SA1019: http.CloseNotifier" linters = ["staticcheck"] text = "SA1019: strings.Title" +# go.opentelemetry.io/otel/exporters/jaeger" is deprecated: This module is no longer supported. OpenTelemetry dropped support for Jaeger exporter in July 2023. +# Jaeger officially accepts and recommends using OTLP. +# Use [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp] or [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc] instead. +[[issues.exclude-rules]] +linters = ["staticcheck"] +text = "SA1019: \"go.opentelemetry.io/otel/exporters/jaeger\"" + [[issues.exclude-rules]] linters = ["staticcheck"] text = "use fake service and real access control evaluator instead" diff --git a/go.mod b/go.mod index c69f43eda6a..0b2dfc7df6d 100644 --- a/go.mod +++ b/go.mod @@ -98,7 +98,7 @@ require ( github.com/grafana/grafana-azure-sdk-go/v2 v2.0.2 // @grafana/partner-datasources github.com/grafana/grafana-google-sdk-go v0.1.0 // @grafana/partner-datasources github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group - github.com/grafana/grafana-plugin-sdk-go v0.227.0 // @grafana/plugins-platform-backend + github.com/grafana/grafana-plugin-sdk-go v0.228.0 // @grafana/plugins-platform-backend github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad // This needs to be here for other projects that import grafana/grafana @@ -153,24 +153,24 @@ require ( github.com/stretchr/testify v1.9.0 // @grafana/grafana-backend-group github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf // @grafana/grafana-backend-group github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f // @grafana/grafana-backend-group - github.com/urfave/cli v1.22.14 // @grafana/grafana-backend-group + github.com/urfave/cli v1.22.15 // @grafana/grafana-backend-group github.com/urfave/cli/v2 v2.25.0 // @grafana/grafana-backend-group github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae // @grafana/grafana-backend-group github.com/wk8/go-ordered-map v1.0.0 // @grafana/grafana-backend-group github.com/xlab/treeprint v1.2.0 // @grafana/observability-traces-and-profiling github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // @grafana/grafana-app-platform-squad github.com/yudai/gojsondiff v1.0.0 // @grafana/grafana-backend-group - go.opentelemetry.io/collector/pdata v1.5.0 // @grafana/grafana-backend-group - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // @grafana/plugins-platform-backend - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0 // @grafana/grafana-operator-experience-squad - go.opentelemetry.io/contrib/propagators/jaeger v1.22.0 // @grafana/grafana-backend-group - go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel v1.24.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/jaeger v1.10.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/sdk v1.24.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/trace v1.24.0 // @grafana/grafana-backend-group + go.opentelemetry.io/collector/pdata v1.6.0 // @grafana/grafana-backend-group + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // @grafana/plugins-platform-backend + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.51.0 // @grafana/grafana-operator-experience-squad + go.opentelemetry.io/contrib/propagators/jaeger v1.26.0 // @grafana/grafana-backend-group + go.opentelemetry.io/contrib/samplers/jaegerremote v0.20.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel v1.26.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/sdk v1.26.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/trace v1.26.0 // @grafana/grafana-backend-group go.uber.org/atomic v1.11.0 // @grafana/alerting-squad-backend go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage gocloud.dev v0.25.0 // @grafana/grafana-app-platform-squad @@ -255,7 +255,7 @@ require ( github.com/buger/jsonparser v1.1.1 // indirect github.com/buildkite/yaml v2.1.0+incompatible // indirect github.com/caio/go-tdigest v3.1.0+incompatible // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/centrifugal/protocol v0.10.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect @@ -264,7 +264,7 @@ require ( github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dennwc/varint v1.0.0 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect @@ -317,7 +317,7 @@ require ( github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db // indirect github.com/grafana/sqlds/v3 v3.2.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect; @grafana/plugins-platform-backend - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect @@ -423,9 +423,9 @@ require ( go.etcd.io/etcd/client/v3 v3.5.10 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect + go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect @@ -433,13 +433,14 @@ require ( golang.org/x/term v0.19.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect; @grafana/grafana-backend-group - google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gotest.tools/v3 v3.5.1 // indirect k8s.io/kms v0.29.2 // indirect lukechampine.com/uint128 v1.3.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect diff --git a/go.sum b/go.sum index a6ba6e09a17..bb7d8040cc4 100644 --- a/go.sum +++ b/go.sum @@ -1512,8 +1512,9 @@ github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -1587,9 +1588,9 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -2172,8 +2173,8 @@ github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkr github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ= github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= -github.com/grafana/grafana-plugin-sdk-go v0.227.0 h1:xkARhSnCovkcDd0n8uwingJID4fAn8tKX7nR2M22ML8= -github.com/grafana/grafana-plugin-sdk-go v0.227.0/go.mod h1:ZhVLifkf1Yyt/I9XjwznANdMGBL2u7/dH/ihyIZ9EA0= +github.com/grafana/grafana-plugin-sdk-go v0.228.0 h1:LlPqyB+RZTtDy8RVYD7iQVJW5A0gMoGSI/+Ykz8HebQ= +github.com/grafana/grafana-plugin-sdk-go v0.228.0/go.mod h1:u4K9vVN6eU86loO68977eTXGypC4brUCnk4sfDzutZU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 h1:hpyusz8c3yRFoJPlA0o34rWnsLbaOOBZleqRhFBi5Lg= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4/go.mod h1:vrRQJuNprTWqwm6JPxHf3BoTJhvO15QMEjQ7Q/YUOnI= github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 h1:tIbI5zgos92vwJ8lV3zwHwuxkV03GR3FGLkFW9V5LxY= @@ -2215,8 +2216,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb 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= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc= @@ -3044,8 +3045,8 @@ github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 h1:4EYQaWAatQokdji3zqZ github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= -github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM= +github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0= github.com/urfave/cli/v2 v2.25.0 h1:ykdZKuQey2zq0yin/l7JOm9Mh+pg72ngYMeB0ABn6q8= github.com/urfave/cli/v2 v2.25.0/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -3154,51 +3155,51 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/collector/featuregate v1.0.0/go.mod h1:xGbRuw+GbutRtVVSEy3YR2yuOlEyiUMhN2M9DJljgqY= go.opentelemetry.io/collector/pdata v1.0.0/go.mod h1:TsDFgs4JLNG7t6x9D8kGswXUz4mme+MyNChHx8zSF6k= -go.opentelemetry.io/collector/pdata v1.5.0 h1:1fKTmUpr0xCOhP/B0VEvtz7bYPQ45luQ8XFyA07j8LE= -go.opentelemetry.io/collector/pdata v1.5.0/go.mod h1:TYj8aKRWZyT/KuKQXKyqSEvK/GV+slFaDMEI+Ke64Yw= +go.opentelemetry.io/collector/pdata v1.6.0 h1:ZIByleLu7ZfHkfPuL8xIMb9M4Gv1R6568LAjhNOO9zY= +go.opentelemetry.io/collector/pdata v1.6.0/go.mod h1:pQv6AJO6wDUDxrPxhNaj3JdSzaOIo5glTGL1b4h4KTg= go.opentelemetry.io/collector/semconv v0.90.1/go.mod h1:j/8THcqVxFna1FpvA2zYIsUperEtOaRaqoLYIN4doWw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0 h1:RtcvQ4iw3w9NBB5yRwgA4sSa82rfId7n4atVpvKx3bY= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0/go.mod h1:f/PbKbRd4cdUICWell6DmzvVJ7QrmBgFrRHjXmAXbK4= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.51.0 h1:974XTyIwHI4nHa1+uSLxHtUnlJ2DiVtAJjk7fd07p/8= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.51.0/go.mod h1:ZvX/taFlN6TGaOOM6D42wrNwPKUV1nGO2FuUXkityBU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/contrib/propagators/jaeger v1.22.0 h1:bAHX+zN/inu+Rbqk51REmC8oXLl+Dw6pp9ldQf/onaY= -go.opentelemetry.io/contrib/propagators/jaeger v1.22.0/go.mod h1:bH9GkgkN21mscXcQP6lQJYI8XnEPDxlTN/ZOBuHDjqE= -go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0 h1:Q9PrD94WoMolBx44ef5UWWvufpVSME0MiSymXZfedso= -go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0/go.mod h1:tjp49JHNvreAAoWjdCHIVD7NXMjuJ3Dp/9iNOuPPlC8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= +go.opentelemetry.io/contrib/propagators/jaeger v1.26.0 h1:RH76Cl2pfOLLoCtxAPax9c7oYzuL1tiI7/ZPJEmEmOw= +go.opentelemetry.io/contrib/propagators/jaeger v1.26.0/go.mod h1:W/cylm0ZtJK1uxsuTqoYGYPnqpZ8CeVGgW7TwfXPsGw= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.20.0 h1:ja+d7Aea/9PgGxB63+E0jtRFpma717wubS0KFkZpmYw= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.20.0/go.mod h1:Yc1eg51SJy7xZdOTyg1xyFcwE+ghcWh3/0hKeLo6Wlo= go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/jaeger v1.10.0 h1:7W3aVVjEYayu/GOqOVF4mbTvnCuxF1wWu3eRxFGQXvw= -go.opentelemetry.io/otel/exporters/jaeger v1.10.0/go.mod h1:n9IGyx0fgyXXZ/i0foLHNxtET9CzXHzZeKCucvRBFgA= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 h1:Waw9Wfpo/IXzOI8bCB7DIk+0JZcqqsyn1JFnAc+iam8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0/go.mod h1:wnJIG4fOqyynOnnQF/eQb4/16VlX2EJAHhHgqIqWfAo= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= go.opentelemetry.io/otel/sdk v1.17.0/go.mod h1:U87sE0f5vQB7hwUoW98pW5Rz4ZDuCFBZFNUBlSgmDFQ= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -4132,8 +4133,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go. google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg= google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= -google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= @@ -4290,8 +4291,9 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go.work.sum b/go.work.sum index 9bec9f3d401..08b659f334b 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,7 +1,9 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230721003620-2341cbb21958.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1 h1:vp9EaPFSb75qe/793x58yE5fY1IJ/gdxb/kcDUzavtI= buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1/go.mod h1:YDq2B5X5BChU0lxAG5MxHpDb8mx1fv9OGtF2mwOe7hY= buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.28.1-20221127060915-a1ecdc58eccd.4 h1:z3Xc9n8yZ5k/Xr4ZTuff76TAYP20dWy7ZBV4cGIpbkM= +cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= cloud.google.com/go/accessapproval v1.7.4 h1:ZvLvJ952zK8pFHINjpMBY5k7LTAp/6pBf50RDMRgBUI= cloud.google.com/go/accessapproval v1.7.5 h1:uzmAMSgYcnlHa9X9YSQZ4Q1wlfl4NNkZyQgho1Z6p04= cloud.google.com/go/accessapproval v1.7.5/go.mod h1:g88i1ok5dvQ9XJsxpUInWWvUBrIZhyPDPbk4T01OoJ0= @@ -9,11 +11,15 @@ cloud.google.com/go/accesscontextmanager v1.8.4 h1:Yo4g2XrBETBCqyWIibN3NHNPQKUfQ cloud.google.com/go/accesscontextmanager v1.8.5 h1:2GLNaNu9KRJhJBFTIVRoPwk6xE5mUDgD47abBq4Zp/I= cloud.google.com/go/accesscontextmanager v1.8.5/go.mod h1:TInEhcZ7V9jptGNqN3EzZ5XMhT6ijWxTGjzyETwmL0Q= cloud.google.com/go/aiplatform v1.57.0 h1:WcZ6wDf/1qBWatmGM9Z+2BTiNjQQX54k2BekHUj93DQ= +cloud.google.com/go/aiplatform v1.57.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= cloud.google.com/go/aiplatform v1.58.0 h1:xyCAfpI4yUMOQ4VtHN/bdmxPQ8xoEkTwFM1nbVmuQhs= +cloud.google.com/go/aiplatform v1.58.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.58.2/go.mod h1:c3kCiVmb6UC1dHAjZjcpDj6ZS0bHQ2slL88ZjC2LtlA= cloud.google.com/go/aiplatform v1.60.0 h1:0cSrii1ZeLr16MbBoocyy5KVnrSdiQ3KN/vtrTe7RqE= cloud.google.com/go/aiplatform v1.60.0/go.mod h1:eTlGuHOahHprZw3Hio5VKmtThIOak5/qy6pzdsqcQnM= cloud.google.com/go/analytics v0.21.6 h1:fnV7B8lqyEYxCU0LKk+vUL7mTlqRAq4uFlIthIdr/iA= cloud.google.com/go/analytics v0.22.0 h1:w8KIgW8NRUHFVKjpkwCpLaHsr685tJ+ckPStOaSCZz0= +cloud.google.com/go/analytics v0.22.0/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= cloud.google.com/go/analytics v0.23.0 h1:Q+y94XH84jM8SK8O7qiY/PJRexb6n7dRbQ6PiUa4YGM= cloud.google.com/go/analytics v0.23.0/go.mod h1:YPd7Bvik3WS95KBok2gPXDqQPHy08TsCQG6CdUCb+u0= cloud.google.com/go/apigateway v1.6.4 h1:VVIxCtVerchHienSlaGzV6XJGtEM9828Erzyr3miUGs= @@ -36,7 +42,10 @@ cloud.google.com/go/artifactregistry v1.14.6 h1:/hQaadYytMdA5zBh+RciIrXZQBWK4vN7 cloud.google.com/go/artifactregistry v1.14.7 h1:W9sVlyb1VRcUf83w7aM3yMsnp4HS4PoyGqYQNG0O5lI= cloud.google.com/go/artifactregistry v1.14.7/go.mod h1:0AUKhzWQzfmeTvT4SjfI4zjot72EMfrkvL9g9aRjnnM= cloud.google.com/go/asset v1.15.3 h1:uI8Bdm81s0esVWbWrTHcjFDFKNOa9aB7rI1vud1hO84= +cloud.google.com/go/asset v1.16.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= cloud.google.com/go/asset v1.17.0 h1:dLWfTnbwyrq/Kt8Tr2JiAbre1MEvS2Bl5cAMiYAy5Pg= +cloud.google.com/go/asset v1.17.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.17.1/go.mod h1:byvDw36UME5AzGNK7o4JnOnINkwOZ1yRrGrKIahHrng= cloud.google.com/go/asset v1.17.2 h1:xgFnBP3luSbUcC9RWJvb3Zkt+y/wW6PKwPHr3ssnIP8= cloud.google.com/go/asset v1.17.2/go.mod h1:SVbzde67ehddSoKf5uebOD1sYw8Ab/jD/9EIeWg99q4= cloud.google.com/go/assuredworkloads v1.11.4 h1:FsLSkmYYeNuzDm8L4YPfLWV+lQaUrJmH5OuD37t1k20= @@ -49,6 +58,7 @@ cloud.google.com/go/baremetalsolution v1.2.3 h1:oQiFYYCe0vwp7J8ZmF6siVKEumWtiPFJ cloud.google.com/go/baremetalsolution v1.2.4 h1:LFydisRmS7hQk9P/YhekwuZGqb45TW4QavcrMToWo5A= cloud.google.com/go/baremetalsolution v1.2.4/go.mod h1:BHCmxgpevw9IEryE99HbYEfxXkAEA3hkMJbYYsHtIuY= cloud.google.com/go/batch v1.7.0 h1:AxuSPoL2fWn/rUyvWeNCNd0V2WCr+iHRCU9QO1PUmpY= +cloud.google.com/go/batch v1.7.0/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= cloud.google.com/go/batch v1.8.0 h1:2HK4JerwVaIcCh/lJiHwh6+uswPthiMMWhiSWLELayk= cloud.google.com/go/batch v1.8.0/go.mod h1:k8V7f6VE2Suc0zUM4WtoibNrA6D3dqBpB+++e3vSGYc= cloud.google.com/go/beyondcorp v1.0.3 h1:VXf9SnrnSmj2BF2cHkoTHvOUp8gjsz1KJFOMW7czdsY= @@ -56,12 +66,15 @@ cloud.google.com/go/beyondcorp v1.0.4 h1:qs0J0O9Ol2h1yA0AU+r7l3hOCPzs2MjE1d6d/ka cloud.google.com/go/beyondcorp v1.0.4/go.mod h1:Gx8/Rk2MxrvWfn4WIhHIG1NV7IBfg14pTKv1+EArVcc= cloud.google.com/go/bigquery v1.57.1 h1:FiULdbbzUxWD0Y4ZGPSVCDLvqRSyCIO6zKV7E2nf5uA= cloud.google.com/go/bigquery v1.58.0 h1:drSd9RcPVLJP2iFMimvOB9SCSIrcl+9HD4II03Oy7A0= +cloud.google.com/go/bigquery v1.58.0/go.mod h1:0eh4mWNY0KrBTjUzLjoYImapGORq9gEPT7MWjCy9lik= cloud.google.com/go/bigquery v1.59.1 h1:CpT+/njKuKT3CEmswm6IbhNu9u35zt5dO4yPDLW+nG4= cloud.google.com/go/bigquery v1.59.1/go.mod h1:VP1UJYgevyTwsV7desjzNzDND5p6hZB+Z8gZJN1GQUc= cloud.google.com/go/billing v1.18.0 h1:GvKy4xLy1zF1XPbwP5NJb2HjRxhnhxjjXxvyZ1S/IAo= +cloud.google.com/go/billing v1.18.0/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= cloud.google.com/go/billing v1.18.2 h1:oWUEQvuC4JvtnqLZ35zgzdbuHt4Itbftvzbe6aEyFdE= cloud.google.com/go/billing v1.18.2/go.mod h1:PPIwVsOOQ7xzbADCwNe8nvK776QpfrOAUkvKjCUcpSE= cloud.google.com/go/binaryauthorization v1.8.0 h1:PHS89lcFayWIEe0/s2jTBiEOtqghCxzc7y7bRNlifBs= +cloud.google.com/go/binaryauthorization v1.8.0/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= cloud.google.com/go/binaryauthorization v1.8.1 h1:1jcyh2uIUwSZkJ/JmL8kd5SUkL/Krbv8zmYLEbAz6kY= cloud.google.com/go/binaryauthorization v1.8.1/go.mod h1:1HVRyBerREA/nhI7yLang4Zn7vfNVA3okoAR9qYQJAQ= cloud.google.com/go/certificatemanager v1.7.4 h1:5YMQ3Q+dqGpwUZ9X5sipsOQ1fLPsxod9HNq0+nrqc6I= @@ -69,6 +82,7 @@ cloud.google.com/go/certificatemanager v1.7.5 h1:UMBr/twXvH3jcT5J5/YjRxf2tvwTYIf cloud.google.com/go/certificatemanager v1.7.5/go.mod h1:uX+v7kWqy0Y3NG/ZhNvffh0kuqkKZIXdvlZRO7z0VtM= cloud.google.com/go/channel v1.17.3 h1:Rd4+fBrjiN6tZ4TR8R/38elkyEkz6oogGDr7jDyjmMY= cloud.google.com/go/channel v1.17.4 h1:yYHOORIM+wkBy3EdwArg/WL7Lg+SoGzlKH9o3Bw2/jE= +cloud.google.com/go/channel v1.17.4/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= cloud.google.com/go/channel v1.17.5 h1:/omiBnyFjm4S1ETHoOmJbL7LH7Ljcei4rYG6Sj3hc80= cloud.google.com/go/channel v1.17.5/go.mod h1:FlpaOSINDAXgEext0KMaBq/vwpLMkkPAw9b2mApQeHc= cloud.google.com/go/cloudbuild v1.15.0 h1:9IHfEMWdCklJ1cwouoiQrnxmP0q3pH7JUt8Hqx4Qbck= @@ -80,12 +94,16 @@ cloud.google.com/go/clouddms v1.7.4/go.mod h1:RdrVqoFG9RWI5AvZ81SxJ/xvxPdtcRhFot cloud.google.com/go/cloudtasks v1.12.4 h1:5xXuFfAjg0Z5Wb81j2GAbB3e0bwroCeSF+5jBn/L650= cloud.google.com/go/cloudtasks v1.12.6 h1:EUt1hIZ9bLv8Iz9yWaCrqgMnIU+Tdh0yXM1MMVGhjfE= cloud.google.com/go/cloudtasks v1.12.6/go.mod h1:b7c7fe4+TJsFZfDyzO51F7cjq7HLUlRi/KZQLQjDsaY= +cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/contactcenterinsights v1.12.1 h1:EiGBeejtDDtr3JXt9W7xlhXyZ+REB5k2tBgVPVtmNb0= +cloud.google.com/go/contactcenterinsights v1.12.1/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= cloud.google.com/go/contactcenterinsights v1.13.0 h1:6Vs/YnDG5STGjlWMEjN/xtmft7MrOTOnOZYUZtGTx0w= cloud.google.com/go/contactcenterinsights v1.13.0/go.mod h1:ieq5d5EtHsu8vhe2y3amtZ+BE+AQwX5qAy7cpo0POsI= cloud.google.com/go/container v1.29.0 h1:jIltU529R2zBFvP8rhiG1mgeTcnT27KhU0H/1d6SQRg= +cloud.google.com/go/container v1.29.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.30.1/go.mod h1:vkbfX0EnAKL/vgVECs5BZn24e1cJROzgszJirRKQ4Bg= cloud.google.com/go/container v1.31.0 h1:MAaNH7VRNPWEhvqOypq2j+7ONJKrKzon4v9nS3nLZe0= cloud.google.com/go/container v1.31.0/go.mod h1:7yABn5s3Iv3lmw7oMmyGbeV6tQj86njcTijkkGuvdZA= cloud.google.com/go/containeranalysis v0.11.3 h1:5rhYLX+3a01drpREqBZVXR9YmWH45RnML++8NsCtuD8= @@ -93,6 +111,7 @@ cloud.google.com/go/containeranalysis v0.11.4 h1:doJ0M1ljS4hS0D2UbHywlHGwB7sQLNr cloud.google.com/go/containeranalysis v0.11.4/go.mod h1:cVZT7rXYBS9NG1rhQbWL9pWbXCKHWJPYraE8/FTSYPE= cloud.google.com/go/datacatalog v1.19.0 h1:rbYNmHwvAOOwnW2FPXYkaK3Mf1MmGqRzK0mMiIEyLdo= cloud.google.com/go/datacatalog v1.19.2 h1:BV5sB7fPc8ccv/obwtHwQtCdLMAgI4KyaQWfkh8/mWg= +cloud.google.com/go/datacatalog v1.19.2/go.mod h1:2YbODwmhpLM4lOFe3PuEhHK9EyTzQJ5AXgIy7EDKTEE= cloud.google.com/go/datacatalog v1.19.3 h1:A0vKYCQdxQuV4Pi0LL9p39Vwvg4jH5yYveMv50gU5Tw= cloud.google.com/go/datacatalog v1.19.3/go.mod h1:ra8V3UAsciBpJKQ+z9Whkxzxv7jmQg1hfODr3N3YPJ4= cloud.google.com/go/dataflow v0.9.4 h1:7VmCNWcPJBS/srN2QnStTB6nu4Eb5TMcpkmtaPVhRt4= @@ -108,7 +127,10 @@ cloud.google.com/go/datalabeling v0.8.4 h1:zrq4uMmunf2KFDl/7dS6iCDBBAxBnKVDyw6+a cloud.google.com/go/datalabeling v0.8.5 h1:GpIFRdm0qIZNsxqURFJwHt0ZBJZ0nF/mUVEigR7PH/8= cloud.google.com/go/datalabeling v0.8.5/go.mod h1:IABB2lxQnkdUbMnQaOl2prCOfms20mcPxDBm36lps+s= cloud.google.com/go/dataplex v1.13.0 h1:ACVOuxwe7gP0SqEso9SLyXbcZNk5l8hjcTX+XLntI5s= +cloud.google.com/go/dataplex v1.13.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= cloud.google.com/go/dataplex v1.14.0 h1:/WhVTR4v/L6ACKjlz/9CqkxkrVh2z7C44CLMUf0f60A= +cloud.google.com/go/dataplex v1.14.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.14.1/go.mod h1:bWxQAbg6Smg+sca2+Ex7s8D9a5qU6xfXtwmq4BVReps= cloud.google.com/go/dataplex v1.14.2 h1:fxIfdU8fxzR3clhOoNI7XFppvAmndxDu1AMH+qX9WKQ= cloud.google.com/go/dataplex v1.14.2/go.mod h1:0oGOSFlEKef1cQeAHXy4GZPB/Ife0fz/PxBf+ZymA2U= cloud.google.com/go/dataproc v1.12.0 h1:W47qHL3W4BPkAIbk4SWmIERwsWBaNnWm0P2sdx3YgGU= @@ -123,18 +145,27 @@ cloud.google.com/go/datastream v1.10.3 h1:Z2sKPIB7bT2kMW5Uhxy44ZgdJzxzE5uKjavoW+ cloud.google.com/go/datastream v1.10.4 h1:o1QDKMo/hk0FN7vhoUQURREuA0rgKmnYapB+1M+7Qz4= cloud.google.com/go/datastream v1.10.4/go.mod h1:7kRxPdxZxhPg3MFeCSulmAJnil8NJGGvSNdn4p1sRZo= cloud.google.com/go/deploy v1.16.0 h1:5OVjzm8MPC5kP+Ywbs0mdE0O7AXvAUXksSyHAyMFyMg= +cloud.google.com/go/deploy v1.16.0/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= cloud.google.com/go/deploy v1.17.0 h1:P3SgJ+4rAktC2XqaI10G0ip/vzWluNBrC5VG0abMbLw= +cloud.google.com/go/deploy v1.17.0/go.mod h1:XBr42U5jIr64t92gcpOXxNrqL2PStQCXHuKK5GRUuYo= cloud.google.com/go/deploy v1.17.1 h1:m27Ojwj03gvpJqCbodLYiVmE9x4/LrHGGMjzc0LBfM4= cloud.google.com/go/deploy v1.17.1/go.mod h1:SXQyfsXrk0fBmgBHRzBjQbZhMfKZ3hMQBw5ym7MN/50= cloud.google.com/go/dialogflow v1.47.0 h1:tLCWad8HZhlyUNfDzDP5m+oH6h/1Uvw/ei7B9AnsWMk= +cloud.google.com/go/dialogflow v1.47.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.48.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= cloud.google.com/go/dialogflow v1.48.1 h1:1Uq2jDJzjJ3M4xYB608FCCFHfW3JmrTmHIxRSd7JGmY= +cloud.google.com/go/dialogflow v1.48.1/go.mod h1:C1sjs2/g9cEwjCltkKeYp3FFpz8BOzNondEaAlCpt+A= +cloud.google.com/go/dialogflow v1.48.2/go.mod h1:7A2oDf6JJ1/+hdpnFRfb/RjJUOh2X3rhIa5P8wQSEX4= cloud.google.com/go/dialogflow v1.49.0 h1:KqG0oxGE71qo0lRVyAoeBozefCvsMfcDzDjoLYSY0F4= cloud.google.com/go/dialogflow v1.49.0/go.mod h1:dhVrXKETtdPlpPhE7+2/k4Z8FRNUp6kMV3EW3oz/fe0= cloud.google.com/go/dlp v1.11.1 h1:OFlXedmPP/5//X1hBEeq3D9kUVm9fb6ywYANlpv/EsQ= cloud.google.com/go/dlp v1.11.2 h1:lTipOuJaSjlYnnotPMbEhKURLC6GzCMDDzVbJAEbmYM= cloud.google.com/go/dlp v1.11.2/go.mod h1:9Czi+8Y/FegpWzgSfkRlyz+jwW6Te9Rv26P3UfU/h/w= cloud.google.com/go/documentai v1.23.6 h1:0/S3AhS23+0qaFe3tkgMmS3STxgDgmE1jg4TvaDOZ9g= +cloud.google.com/go/documentai v1.23.6/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= cloud.google.com/go/documentai v1.23.7 h1:hlYieOXUwiJ7HpBR/vEPfr8nfSxveLVzbqbUkSK0c/4= +cloud.google.com/go/documentai v1.23.7/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.23.8/go.mod h1:Vd/y5PosxCpUHmwC+v9arZyeMfTqBR9VIwOwIqQYYfA= cloud.google.com/go/documentai v1.25.0 h1:lI62GMEEPO6vXJI9hj+G9WjOvnR0hEjvjokrnex4cxA= cloud.google.com/go/documentai v1.25.0/go.mod h1:ftLnzw5VcXkLItp6pw1mFic91tMRyfv6hHEY5br4KzY= cloud.google.com/go/domains v0.9.4 h1:ua4GvsDztZ5F3xqjeLKVRDeOvJshf5QFgWGg1CKti3A= @@ -169,10 +200,12 @@ cloud.google.com/go/gkehub v0.14.5 h1:RboLNFzf9wEMSo7DrKVBlf+YhK/A/jrLN454L5Tz99 cloud.google.com/go/gkehub v0.14.5/go.mod h1:6bzqxM+a+vEH/h8W8ec4OJl4r36laxTs3A/fMNHJ0wA= cloud.google.com/go/gkemulticloud v1.0.3 h1:NmJsNX9uQ2CT78957xnjXZb26TDIMvv+d5W2vVUt0Pg= cloud.google.com/go/gkemulticloud v1.1.0 h1:C2Suwn3uPz+Yy0bxVjTlsMrUCaDovkgvfdyIa+EnUOU= +cloud.google.com/go/gkemulticloud v1.1.0/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= cloud.google.com/go/gkemulticloud v1.1.1 h1:rsSZAGLhyjyE/bE2ToT5fqo1qSW7S+Ubsc9jFOcbhSI= cloud.google.com/go/gkemulticloud v1.1.1/go.mod h1:C+a4vcHlWeEIf45IB5FFR5XGjTeYhF83+AYIpTy4i2Q= cloud.google.com/go/grafeas v0.3.0 h1:oyTL/KjiUeBs9eYLw/40cpSZglUC+0F7X4iu/8t7NWs= cloud.google.com/go/grafeas v0.3.4 h1:D4x32R/cHX3MTofKwirz015uEdVk4uAxvZkZCZkOrF4= +cloud.google.com/go/grafeas v0.3.4/go.mod h1:A5m316hcG+AulafjAbPKXBO/+I5itU4LOdKO2R/uDIc= cloud.google.com/go/gsuiteaddons v1.6.4 h1:uuw2Xd37yHftViSI8J2hUcCS8S7SH3ZWH09sUDLW30Q= cloud.google.com/go/gsuiteaddons v1.6.5 h1:CZEbaBwmbYdhFw21Fwbo+C35HMe36fTE0FBSR4KSfWg= cloud.google.com/go/gsuiteaddons v1.6.5/go.mod h1:Lo4P2IvO8uZ9W+RaC6s1JVxo42vgy+TX5a6hfBZ0ubs= @@ -185,6 +218,7 @@ cloud.google.com/go/ids v1.4.5/go.mod h1:p0ZnyzjMWxww6d2DvMGnFwCsSxDJM666Iir1bK1 cloud.google.com/go/iot v1.7.4 h1:m1WljtkZnvLTIRYW1YTOv5A6H1yKgLHR6nU7O8yf27w= cloud.google.com/go/iot v1.7.5 h1:munTeBlbqI33iuTYgXy7S8lW2TCgi5l1hA4roSIY+EE= cloud.google.com/go/iot v1.7.5/go.mod h1:nq3/sqTz3HGaWJi1xNiX7F41ThOzpud67vwk0YsSsqs= +cloud.google.com/go/kms v1.15.6/go.mod h1:yF75jttnIdHfGBoE51AKsD/Yqf+/jICzB9v1s1acsms= cloud.google.com/go/language v1.12.2 h1:zg9uq2yS9PGIOdc0Kz/l+zMtOlxKWonZjjo5w5YPG2A= cloud.google.com/go/language v1.12.3 h1:iaJZg6K4j/2PvZZVcjeO/btcWWIllVRBhuTFjGO4LXs= cloud.google.com/go/language v1.12.3/go.mod h1:evFX9wECX6mksEva8RbRnr/4wi/vKGYnAJrTRXU8+f8= @@ -201,7 +235,9 @@ cloud.google.com/go/managedidentities v1.6.4 h1:SF/u1IJduMqQQdJA4MDyivlIQ4SrV5qA cloud.google.com/go/managedidentities v1.6.5 h1:+bpih1piZVLxla/XBqeSUzJBp8gv9plGHIMAI7DLpDM= cloud.google.com/go/managedidentities v1.6.5/go.mod h1:fkFI2PwwyRQbjLxlm5bQ8SjtObFMW3ChBGNqaMcgZjI= cloud.google.com/go/maps v1.6.2 h1:WxxLo//b60nNFESefLgaBQevu8QGUmRV3+noOjCfIHs= +cloud.google.com/go/maps v1.6.2/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= cloud.google.com/go/maps v1.6.3 h1:Qqs6Dza+PRp5CZO5AfgPnLwU1k3pp0IMFRDtLpT+aCA= +cloud.google.com/go/maps v1.6.3/go.mod h1:VGAn809ADswi1ASofL5lveOHPnE6Rk/SFTTBx1yuOLw= cloud.google.com/go/maps v1.6.4 h1:EVCZAiDvog9So46460BGbCasPhi613exoaQbpilMVlk= cloud.google.com/go/maps v1.6.4/go.mod h1:rhjqRy8NWmDJ53saCfsXQ0LKwBHfi6OSh5wkq6BaMhI= cloud.google.com/go/mediatranslation v0.8.4 h1:VRCQfZB4s6jN0CSy7+cO3m4ewNwgVnaePanVCQh/9Z4= @@ -215,6 +251,8 @@ cloud.google.com/go/metastore v1.13.4 h1:dR7vqWXlK6IYR8Wbu9mdFfwlVjodIBhd1JRrpZf cloud.google.com/go/metastore v1.13.4/go.mod h1:FMv9bvPInEfX9Ac1cVcRXp8EBBQnBcqH6gz3KvJ9BAE= cloud.google.com/go/monitoring v1.16.3 h1:mf2SN9qSoBtIgiMA4R/y4VADPWZA7VCNJA079qLaZQ8= cloud.google.com/go/monitoring v1.17.0 h1:blrdvF0MkPPivSO041ihul7rFMhXdVp8Uq7F59DKXTU= +cloud.google.com/go/monitoring v1.17.0/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= +cloud.google.com/go/monitoring v1.17.1/go.mod h1:SJzPMakCF0GHOuKEH/r4hxVKF04zl+cRPQyc3d/fqII= cloud.google.com/go/monitoring v1.18.0 h1:NfkDLQDG2UR3WYZVQE8kwSbUIEyIqJUPl+aOQdFH1T4= cloud.google.com/go/monitoring v1.18.0/go.mod h1:c92vVBCeq/OB4Ioyo+NbN2U7tlg5ZH41PZcdvfc+Lcg= cloud.google.com/go/networkconnectivity v1.14.3 h1:e9lUkCe2BexsqsUc2bjV8+gFBpQa54J+/F3qKVtW+wA= @@ -237,6 +275,7 @@ cloud.google.com/go/orchestration v1.8.5 h1:YHgWMlrPttIVGItgGfuvO2KM7x+y9ivN/Yk9 cloud.google.com/go/orchestration v1.8.5/go.mod h1:C1J7HesE96Ba8/hZ71ISTV2UAat0bwN+pi85ky38Yq8= cloud.google.com/go/orgpolicy v1.11.4 h1:RWuXQDr9GDYhjmrredQJC7aY7cbyqP9ZuLbq5GJGves= cloud.google.com/go/orgpolicy v1.12.0 h1:sab7cDiyfdthpAL0JkSpyw1C3mNqkXToVOhalm79PJQ= +cloud.google.com/go/orgpolicy v1.12.0/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= cloud.google.com/go/orgpolicy v1.12.1 h1:2JbXigqBJVp8Dx5dONUttFqewu4fP0p3pgOdIZAhpYU= cloud.google.com/go/orgpolicy v1.12.1/go.mod h1:aibX78RDl5pcK3jA8ysDQCFkVxLj3aOQqrbBaUL2V5I= cloud.google.com/go/osconfig v1.12.4 h1:OrRCIYEAbrbXdhm13/JINn9pQchvTTIzgmOCA7uJw8I= @@ -244,6 +283,7 @@ cloud.google.com/go/osconfig v1.12.5 h1:Mo5jGAxOMKH/PmDY7fgY19yFcVbvwREb5D5zMPQj cloud.google.com/go/osconfig v1.12.5/go.mod h1:D9QFdxzfjgw3h/+ZaAb5NypM8bhOMqBzgmbhzWViiW8= cloud.google.com/go/oslogin v1.12.2 h1:NP/KgsD9+0r9hmHC5wKye0vJXVwdciv219DtYKYjgqE= cloud.google.com/go/oslogin v1.13.0 h1:gbA/G4p+youIR4O/Rk6DU181QlBlpwPS16kvJwqEz8o= +cloud.google.com/go/oslogin v1.13.0/go.mod h1:xPJqLwpTZ90LSE5IL1/svko+6c5avZLluiyylMb/sRA= cloud.google.com/go/oslogin v1.13.1 h1:1K4nOT5VEZNt7XkhaTXupBYos5HjzvJMfhvyD2wWdFs= cloud.google.com/go/oslogin v1.13.1/go.mod h1:vS8Sr/jR7QvPWpCjNqy6LYZr5Zs1e8ZGW/KPn9gmhws= cloud.google.com/go/phishingprotection v0.8.4 h1:sPLUQkHq6b4AL0czSJZ0jd6vL55GSTHz2B3Md+TCZI0= @@ -257,11 +297,13 @@ cloud.google.com/go/privatecatalog v0.9.5 h1:UZ0assTnATXSggoxUIh61RjTQ4P9zCMk/kE cloud.google.com/go/privatecatalog v0.9.5/go.mod h1:fVWeBOVe7uj2n3kWRGlUQqR/pOd450J9yZoOECcQqJk= cloud.google.com/go/pubsub v1.33.0 h1:6SPCPvWav64tj0sVX/+npCBKhUi/UjJehy9op/V3p2g= cloud.google.com/go/pubsub v1.34.0 h1:ZtPbfwfi5rLaPeSvDC29fFoE20/tQvGrUS6kVJZJvkU= +cloud.google.com/go/pubsub v1.34.0/go.mod h1:alj4l4rBg+N3YTFDDC+/YyFTs6JAjam2QfYsddcAW4c= cloud.google.com/go/pubsub v1.36.1 h1:dfEPuGCHGbWUhaMCTHUFjfroILEkx55iUmKBZTP5f+Y= cloud.google.com/go/pubsub v1.36.1/go.mod h1:iYjCa9EzWOoBiTdd4ps7QoMtMln5NwaZQpK1hbRfBDE= cloud.google.com/go/pubsublite v1.8.1 h1:pX+idpWMIH30/K7c0epN6V703xpIcMXWRjKJsz0tYGY= cloud.google.com/go/recaptchaenterprise v1.3.1 h1:u6EznTGzIdsyOsvm+Xkw0aSuKFXQlyjGE9a4exk6iNQ= cloud.google.com/go/recaptchaenterprise/v2 v2.9.0 h1:Zrd4LvT9PaW91X/Z13H0i5RKEv9suCLuk8zp+bfOpN4= +cloud.google.com/go/recaptchaenterprise/v2 v2.9.0/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= cloud.google.com/go/recaptchaenterprise/v2 v2.9.2 h1:U3Wfq12X9cVMuTpsWDSURnXF0Z9hSPTHj+xsnXDRLsw= cloud.google.com/go/recaptchaenterprise/v2 v2.9.2/go.mod h1:trwwGkfhCmp05Ll5MSJPXY7yvnO0p4v3orGANAFHAuU= cloud.google.com/go/recommendationengine v0.8.4 h1:JRiwe4hvu3auuh2hujiTc2qNgPPfVp+Q8KOpsXlEzKQ= @@ -269,6 +311,7 @@ cloud.google.com/go/recommendationengine v0.8.5 h1:ineqLswaCSBY0csYv5/wuXJMBlxAT cloud.google.com/go/recommendationengine v0.8.5/go.mod h1:A38rIXHGFvoPvmy6pZLozr0g59NRNREz4cx7F58HAsQ= cloud.google.com/go/recommender v1.11.3 h1:VndmgyS/J3+izR8V8BHa7HV/uun8//ivQ3k5eVKKyyM= cloud.google.com/go/recommender v1.12.0 h1:tC+ljmCCbuZ/ybt43odTFlay91n/HLIhflvaOeb0Dh4= +cloud.google.com/go/recommender v1.12.0/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= cloud.google.com/go/recommender v1.12.1 h1:LVLYS3r3u0MSCxQSDUtLSkporEGi9OAE6hGvayrZNPs= cloud.google.com/go/recommender v1.12.1/go.mod h1:gf95SInWNND5aPas3yjwl0I572dtudMhMIG4ni8nr+0= cloud.google.com/go/redis v1.14.1 h1:J9cEHxG9YLmA9o4jTSvWt/RuVEn6MTrPlYSCRHujxDQ= @@ -281,6 +324,7 @@ cloud.google.com/go/resourcesettings v1.6.4 h1:yTIL2CsZswmMfFyx2Ic77oLVzfBFoWBYg cloud.google.com/go/resourcesettings v1.6.5 h1:BTr5MVykJwClASci/7Og4Qfx70aQ4n3epsNLj94ZYgw= cloud.google.com/go/resourcesettings v1.6.5/go.mod h1:WBOIWZraXZOGAgoR4ukNj0o0HiSMO62H9RpFi9WjP9I= cloud.google.com/go/retail v1.14.4 h1:geqdX1FNqqL2p0ADXjPpw8lq986iv5GrVcieTYafuJQ= +cloud.google.com/go/retail v1.15.1/go.mod h1:In9nSBOYhLbDGa87QvWlnE1XA14xBN2FpQRiRsUs9wU= cloud.google.com/go/retail v1.16.0 h1:Fn1GuAua1c6crCGqfJ1qMxG1Xh10Tg/x5EUODEHMqkw= cloud.google.com/go/retail v1.16.0/go.mod h1:LW7tllVveZo4ReWt68VnldZFWJRzsh9np+01J9dYWzE= cloud.google.com/go/run v1.3.3 h1:qdfZteAm+vgzN1iXzILo3nJFQbzziudkJrvd9wCf3FQ= @@ -296,6 +340,7 @@ cloud.google.com/go/security v1.15.4 h1:sdnh4Islb1ljaNhpIXlIPgb3eYj70QWgPVDKOUYv cloud.google.com/go/security v1.15.5 h1:wTKJQ10j8EYgvE8Y+KhovxDRVDk2iv/OsxZ6GrLP3kE= cloud.google.com/go/security v1.15.5/go.mod h1:KS6X2eG3ynWjqcIX976fuToN5juVkF6Ra6c7MPnldtc= cloud.google.com/go/securitycenter v1.24.3 h1:crdn2Z2rFIy8WffmmhdlX3CwZJusqCiShtnrGFRwpeE= +cloud.google.com/go/securitycenter v1.24.3/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= cloud.google.com/go/securitycenter v1.24.4 h1:/5jjkZ+uGe8hZ7pvd7pO30VW/a+pT2MrrdgOqjyucKQ= cloud.google.com/go/securitycenter v1.24.4/go.mod h1:PSccin+o1EMYKcFQzz9HMMnZ2r9+7jbc+LvPjXhpwcU= cloud.google.com/go/servicecontrol v1.11.1 h1:d0uV7Qegtfaa7Z2ClDzr9HJmnbJW7jn0WhZ7wOX6hLE= @@ -308,12 +353,17 @@ cloud.google.com/go/shell v1.7.4 h1:nurhlJcSVFZneoRZgkBEHumTYf/kFJptCK2eBUq/88M= cloud.google.com/go/shell v1.7.5 h1:3Fq2hzO0ZSyaqBboJrFkwwf/qMufDtqwwA6ep8EZxEI= cloud.google.com/go/shell v1.7.5/go.mod h1:hL2++7F47/IfpfTO53KYf1EC+F56k3ThfNEXd4zcuiE= cloud.google.com/go/spanner v1.53.1 h1:xNmE0SXMSxNBuk7lRZ5G/S+A49X91zkSTt7Jn5Ptlvw= +cloud.google.com/go/spanner v1.53.1/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= +cloud.google.com/go/spanner v1.54.0/go.mod h1:wZvSQVBgngF0Gq86fKup6KIYmN2be7uOKjtK97X+bQU= cloud.google.com/go/spanner v1.55.0 h1:YF/A/k73EMYCjp8wcJTpkE+TcrWutHRlsCtlRSfWS64= +cloud.google.com/go/spanner v1.55.0/go.mod h1:HXEznMUVhC+PC+HDyo9YFG2Ajj5BQDkcbqB9Z2Ffxi0= +cloud.google.com/go/spanner v1.56.0/go.mod h1:DndqtUKQAt3VLuV2Le+9Y3WTnq5cNKrnLb/Piqcj+h0= cloud.google.com/go/spanner v1.57.0 h1:fJq+ZfQUDHE+cy1li0bJA8+sy2oiSGhuGqN5nqVaZdU= cloud.google.com/go/spanner v1.57.0/go.mod h1:aXQ5QDdhPRIqVhYmnkAdwPYvj/DRN0FguclhEWw+jOo= cloud.google.com/go/speech v1.21.0 h1:qkxNao58oF8ghAHE1Eghen7XepawYEN5zuZXYWaUTA4= cloud.google.com/go/speech v1.21.1 h1:nuFc+Kj5B8de75nN4FdPyUbI2SiBoHZG6BLurXL56Q0= cloud.google.com/go/speech v1.21.1/go.mod h1:E5GHZXYQlkqWQwY5xRSLHw2ci5NMQNG52FfMU1aZrIA= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= cloud.google.com/go/storagetransfer v1.10.3 h1:YM1dnj5gLjfL6aDldO2s4GeU8JoAvH1xyIwXre63KmI= cloud.google.com/go/storagetransfer v1.10.4 h1:dy4fL3wO0VABvzM05ycMUPFHxTPbJz9Em8ikAJVqSbI= cloud.google.com/go/storagetransfer v1.10.4/go.mod h1:vef30rZKu5HSEf/x1tK3WfWrL0XVoUQN/EPDRGPzjZs= @@ -331,6 +381,7 @@ cloud.google.com/go/trace v1.10.5 h1:0pr4lIKJ5XZFYD9GtxXEWr0KkVeigc3wlGpZco0X1oA cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M= cloud.google.com/go/translate v1.9.3 h1:t5WXTqlrk8VVJu/i3WrYQACjzYJiff5szARHiyqqPzI= cloud.google.com/go/translate v1.10.0 h1:tncNaKmlZnayMMRX/mMM2d5AJftecznnxVBD4w070NI= +cloud.google.com/go/translate v1.10.0/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= cloud.google.com/go/translate v1.10.1 h1:upovZ0wRMdzZvXnu+RPam41B0mRJ+coRXFP2cYFJ7ew= cloud.google.com/go/translate v1.10.1/go.mod h1:adGZcQNom/3ogU65N9UXHOnnSvjPwA/jKQUMnsYXOyk= cloud.google.com/go/video v1.20.3 h1:Xrpbm2S9UFQ1pZEeJt9Vqm5t2T/z9y/M3rNXhFoo8Is= @@ -341,6 +392,7 @@ cloud.google.com/go/videointelligence v1.11.5 h1:mYaWH8uhUCXLJCN3gdXswKzRa2+lK0z cloud.google.com/go/videointelligence v1.11.5/go.mod h1:/PkeQjpRponmOerPeJxNPuxvi12HlW7Em0lJO14FC3I= cloud.google.com/go/vision v1.2.0 h1:/CsSTkbmO9HC8iQpxbK8ATms3OQaX3YQUeTMGCxlaK4= cloud.google.com/go/vision/v2 v2.7.5 h1:T/ujUghvEaTb+YnFY/jiYwVAkMbIC8EieK0CJo6B4vg= +cloud.google.com/go/vision/v2 v2.7.6/go.mod h1:ZkvWTVNPBU3YZYzgF9Y1jwEbD1NBOCyJn0KFdQfE6Bw= cloud.google.com/go/vision/v2 v2.8.0 h1:W52z1b6LdGI66MVhE70g/NFty9zCYYcjdKuycqmlhtg= cloud.google.com/go/vision/v2 v2.8.0/go.mod h1:ocqDiA2j97pvgogdyhoxiQp2ZkDCyr0HWpicywGGRhU= cloud.google.com/go/vmmigration v1.7.4 h1:qPNdab4aGgtaRX+51jCOtJxlJp6P26qua4o1xxUDjpc= @@ -415,24 +467,32 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vaj github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19 h1:iXUgAaqDcIUGbRoy2TdeofRG/j1zpGRSEmNK05T+bi8= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= +github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0= +github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kong v0.2.11 h1:RKeJXXWfg9N47RYfMm0+igkxBCTF4bzbneAxaqid0c4= github.com/alecthomas/kong v0.2.11/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= +github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y= github.com/alecthomas/participle/v2 v2.1.0 h1:z7dElHRrOEEq45F2TG5cbQihMtNTv8vwldytDj7Wrz4= github.com/alecthomas/participle/v2 v2.1.0/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= +github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/apache/arrow/go/v10 v10.0.1 h1:n9dERvixoC/1JjDmBcs9FPaEryoANa2sCgVFo6ez9cI= github.com/apache/arrow/go/v11 v11.0.0 h1:hqauxvFQxww+0mEU/2XHG6LT7eZternCZq+A5Yly2uM= github.com/apache/arrow/go/v12 v12.0.0 h1:xtZE63VWl7qLdB0JObIXvvhGjoVNrQ9ciIHG2OK5cmc= github.com/apache/arrow/go/v12 v12.0.1 h1:JsR2+hzYYjgSUkBSaahpqCetqZMr76djX80fF/DiJbg= +github.com/apache/arrow/go/v12 v12.0.1/go.mod h1:weuTY7JvTG/HDPtMQxEUp7pU73vkLWMLpY67QwZ/WWw= github.com/apache/arrow/go/v13 v13.0.0 h1:kELrvDQuKZo8csdWYqBQfyi431x6Zs/YJTEgUuSVcWk= github.com/apache/arrow/go/v13 v13.0.0/go.mod h1:W69eByFNO0ZR30q1/7Sr9d83zcVZmF2MiP3fFYAWJOc= github.com/apache/arrow/go/v14 v14.0.2 h1:N8OkaJEOfI3mEZt07BIkvo4sC6XDbL+48MBPWO5IONw= +github.com/apache/arrow/go/v14 v14.0.2/go.mod h1:u3fgh3EdgN/YQ8cVQRguVW3R+seMybFg8QBQ5LU+eBY= +github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= @@ -465,8 +525,11 @@ github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY= github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= @@ -474,6 +537,7 @@ github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec h1:EdRZT3IeKQmfCSrgo8SZ8V3MEnskuJP0wCYNpe+aiXo= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= @@ -537,11 +601,13 @@ github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM= github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= +github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 h1:DddqAaWDpywytcG8w/qoQ5sAN8X12d3Z3koB0C3Rxsc= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= @@ -608,6 +674,7 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54= github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4 h1:vF83LI8tAakwEwvWZtrIEx7pOySacl2TOxx6eXk4ePo= @@ -624,11 +691,14 @@ github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= +github.com/google/cel-go v0.17.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/go-jsonnet v0.18.0 h1:/6pTy6g+Jh1a1I2UMoAODkqELFiVIdOxbNwv0DDzoOg= github.com/google/go-jsonnet v0.18.0/go.mod h1:C3fTzyVJDslXdiTqw/bTFk7vSGyCtH3MGRbDfvEwGd0= github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9 h1:OF1IPgv+F4NmqmJ98KTjdN97Vs1JxDPB3vbmYzV2dpk= github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE= github.com/google/go-replayers/httpreplay v1.1.1 h1:H91sIMlt1NZzN7R+/ASswyouLJfW0WLW7fhyUFvDEkY= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -640,6 +710,7 @@ github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grafana/authlib v0.0.0-20240319083410-9d4a6e3861e5/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4= github.com/grafana/dataplane/sdata v0.0.7 h1:CImITypIyS1jxijCR6xqKx71JnYAxcwpH9ChK0gH164= github.com/grafana/dataplane/sdata v0.0.7/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= @@ -652,23 +723,34 @@ github.com/grafana/grafana-azure-sdk-go/v2 v2.0.2/go.mod h1:s8GLONgVh/svnSsO0Eo+ github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJf9zRdVxWV/W4Wi+Ox6gifgs= github.com/grafana/grafana-plugin-sdk-go v0.215.0/go.mod h1:nBsh3jRItKQUXDF2BQkiQCPxqrsSQeb+7hiFyJTO1RE= github.com/grafana/grafana-plugin-sdk-go v0.216.0/go.mod h1:FdvSvOliqpVLnytM7e89zCFyYPDE6VOn9SIjVQRvVxM= +github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240426134450-5fe9f7b9dfd4 h1:GV9u4RplRyMlqDicJ0t+m1nVTL1SSfqHd38B/RGum+k= +github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240426134450-5fe9f7b9dfd4/go.mod h1:UBDIuvdUGUI5fMDHDAl6yAVpFhfwl5ojMaw1N68775w= +github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240430073540-ce4d126ae8b8 h1:pyWJN79uW8QHZiQRasHGLCEkXSr3k6HCjdr0J2jZ3rU= +github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240430073540-ce4d126ae8b8/go.mod h1:u4K9vVN6eU86loO68977eTXGypC4brUCnk4sfDzutZU= +github.com/grafana/grafana-plugin-sdk-go v0.228.0 h1:LlPqyB+RZTtDy8RVYD7iQVJW5A0gMoGSI/+Ykz8HebQ= +github.com/grafana/grafana-plugin-sdk-go v0.228.0/go.mod h1:u4K9vVN6eU86loO68977eTXGypC4brUCnk4sfDzutZU= github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o= github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hudl/fargo v1.4.0 h1:ZDDILMbB37UlAVLlWcJ2Iz1XuahZZTDZfdCKeclfq2s= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91 h1:KyZDvZ/GGn+r+Y3DKZ7UOQ/TP4xV6HNkrwiVMB1GnNY= github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU= github.com/influxdata/influxdb v1.7.6 h1:8mQ7A/V+3noMGCt/P9pD09ISaiz9XvgCk303UYA3gcs= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig= @@ -724,6 +806,7 @@ github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIR github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= @@ -765,6 +848,7 @@ github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/matryer/moq v0.3.1 h1:kLDiBJoGcusWS2BixGyTkF224aSCD8nLY24tj/NcTCs= github.com/matryer/moq v0.3.1/go.mod h1:RJ75ZZZD71hejp39j4crZLsEDszGk6iH4v4YsWFKH4s= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/mediocregopher/radix/v3 v3.8.1/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= @@ -853,6 +937,7 @@ github.com/phpdave11/gofpdi v1.0.13 h1:o61duiW8M9sMlkVXWlvP92sZJtGKENvW3VExs6dZu github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= @@ -860,6 +945,7 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM= github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= @@ -882,8 +968,6 @@ github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1Avp github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/scottlepp/go-duck v0.0.19 h1:SjO0HF+xe6TN9agMram+CG8+NWKgGMSj8LfqRm0JvpA= -github.com/scottlepp/go-duck v0.0.19/go.mod h1:GL+hHuKdueJRrFCduwBc7A7TQk+Tetc5BPXPVtduihY= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e h1:uO75wNGioszjmIzcY/tvdDYKRLVvzggtAmmJkn9j4GQ= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZFQ8e24NYaBGIlnO2WGCAi67re4HHuOm0sftE/M= github.com/segmentio/parquet-go v0.0.0-20230427215636-d483faba23a5 h1:7CWCjaHrXSUCHrRhIARMGDVKdB82tnPAQMmANeflKOw= @@ -901,8 +985,10 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e h1:mOtuXaRAbVZsxAHVdPR3IjfmN8T1h2iczJLynhLybf8= github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= @@ -928,6 +1014,8 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o= github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/valyala/fasthttp v1.6.0 h1:uWF8lgKmeaIewWVPwi4GRq2P6+R46IgYZdxWtM+GtEY= github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= @@ -976,6 +1064,7 @@ github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= +go.einride.tech/aip v0.66.0/go.mod h1:qAhMsfT7plxBX+Oy7Huol6YUvZ0ZzdUz26yZsQwfl1M= go.opentelemetry.io/collector v0.74.0 h1:0s2DKWczGj/pLTsXGb1P+Je7dyuGx9Is4/Dri1+cS7g= go.opentelemetry.io/collector v0.74.0/go.mod h1:7NjZAvkhQ6E+NLN4EAH2hw3Nssi+F14t7mV7lMNXCto= go.opentelemetry.io/collector/component v0.74.0 h1:W32ILPgbA5LO+m9Se61hbbtiLM6FYusNM36K5/CCOi0= @@ -996,12 +1085,18 @@ go.opentelemetry.io/collector/receiver/otlpreceiver v0.74.0/go.mod h1:9X9/RYFxJI go.opentelemetry.io/collector/semconv v0.90.1 h1:2fkQZbefQBbIcNb9Rk1mRcWlFZgQOk7CpST1e1BK8eg= go.opentelemetry.io/contrib v0.18.0 h1:uqBh0brileIvG6luvBjdxzoFL8lxDGuhxJWsvK3BveI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0/go.mod h1:5z+/ZWJQKXa9YT34fQNx5K8Hd1EoIhvtUygUQPqEOgQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1/go.mod h1:GnOaBaFQ2we3b9AGWJpsBa7v1S5RlQzlC3O7dRMxZhM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/contrib/propagators/b3 v1.15.0 h1:bMaonPyFcAvZ4EVzkUNkfnUHP5Zi63CIDlA3dRsEg8Q= go.opentelemetry.io/contrib/propagators/b3 v1.15.0/go.mod h1:VjU0g2v6HSQ+NwfifambSLAeBgevjIcqmceaKWEzl0c= go.opentelemetry.io/contrib/samplers/jaegerremote v0.16.0/go.mod h1:StxwPndBVNZD2sZez0RQ0SP/129XGCd4aEmVGaw1/QM= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/bridge/opencensus v0.37.0 h1:ieH3gw7b1eg90ARsFAlAsX5LKVZgnCYfaDwRrK6xLHU= go.opentelemetry.io/otel/bridge/opencensus v0.37.0/go.mod h1:ddiK+1PE68l/Xk04BGTh9Y6WIcxcLrmcVxVlS0w5WZ0= go.opentelemetry.io/otel/bridge/opentracing v1.10.0 h1:WzAVGovpC1s7KD5g4taU6BWYZP3QGSDVTlbRu9fIHw8= @@ -1009,11 +1104,16 @@ go.opentelemetry.io/otel/bridge/opentracing v1.10.0/go.mod h1:J7GLR/uxxqMAzZptsH go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= go.opentelemetry.io/otel/exporters/prometheus v0.37.0 h1:NQc0epfL0xItsmGgSXgfbH2C1fq2VLXkZoDFsfRNHpc= go.opentelemetry.io/otel/exporters/prometheus v0.37.0/go.mod h1:hB8qWjsStK36t50/R0V2ULFb4u95X/Q6zupXLgvjTh8= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= @@ -1024,42 +1124,86 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEa go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808 h1:+Kc94D8UVEVxJnLXp/+FMfqQARZtWHfVrcRtcG8aT3g= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4= +google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= +google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= +google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= +google.golang.org/api v0.160.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw= google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= +google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= +google.golang.org/genproto/googleapis/api v0.0.0-20240122161410-6c6643bf1457/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:PVreiBMirk8ypES6aw9d4p6iiBNSIfZEBqr3UGoAi2E= google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231120223509-83a465c0220f h1:hL+1ptbhFoeL1HcROQ8OGXaqH0jYRRibgWQWco0/Ugc= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0 h1:Y6QQt9D/syZt/Qgnz5a1y2O3WunQeeVDfS9+Xr82iFA= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0/go.mod h1:guYXGPwC6jwxgWKW5Y405fKWOFNwlvUlUnzyp9i0uqo= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:ZSvZ8l+AWJwXw91DoTjWjaVLpWU6o0eZ4YLYpH8aLeQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe h1:weYsP+dNijSQVoLAb5bpUos3ciBpNU/NEVlHFKrk8pg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa h1:wBkzraZsSqhj1M4L/nMrljUU6XasJkgHvUsq8oRGwF0= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:IN9OQUXZ0xT+26MDwZL8fJcYw+y99b0eYPA2U15Jt8o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240122161410-6c6643bf1457/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415141817-7cd4c1c1f9ec/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= +google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= @@ -1079,6 +1223,10 @@ k8s.io/kms v0.29.0/go.mod h1:mB0f9HLxRXeXUfHfn1A7rpwOlzXI1gIWu86z6buNoYA= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kube-openapi v0.0.0-20231214164306-ab13479f8bf8/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= +modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= +modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0= +modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= +modernc.org/libc v1.21.2/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 48875edba5d..104d63af55c 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -5,7 +5,7 @@ go 1.21.0 require ( github.com/bwmarrin/snowflake v0.3.0 github.com/gorilla/mux v1.8.1 - github.com/grafana/grafana-plugin-sdk-go v0.227.0 + github.com/grafana/grafana-plugin-sdk-go v0.228.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f github.com/stretchr/testify v1.9.0 golang.org/x/mod v0.15.0 @@ -26,13 +26,13 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect @@ -63,7 +63,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -108,23 +108,23 @@ require ( github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect github.com/unknwon/com v1.0.1 // indirect github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 // indirect - github.com/urfave/cli v1.22.14 // indirect + github.com/urfave/cli v1.22.15 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.etcd.io/etcd/api/v3 v3.5.10 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect go.etcd.io/etcd/client/v3 v3.5.10 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/contrib/propagators/jaeger v1.22.0 // indirect - go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/sdk v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.51.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.26.0 // indirect + go.opentelemetry.io/contrib/samplers/jaegerremote v0.20.0 // indirect + go.opentelemetry.io/otel v1.26.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 // indirect + go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/otel/sdk v1.26.0 // indirect + go.opentelemetry.io/otel/trace v1.26.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.22.0 // indirect @@ -138,7 +138,7 @@ require ( golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.18.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 68c5a976c7b..703e5d9c0ef 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -19,8 +19,7 @@ github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZ github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= @@ -34,9 +33,8 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -127,7 +125,7 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/grafana-plugin-sdk-go v0.227.0 h1:xkARhSnCovkcDd0n8uwingJID4fAn8tKX7nR2M22ML8= +github.com/grafana/grafana-plugin-sdk-go v0.228.0 h1:LlPqyB+RZTtDy8RVYD7iQVJW5A0gMoGSI/+Ykz8HebQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f h1:+CK3tH3XrAAqx5urmVqpgSxMrL2MlpTOnLVSU4w4IjY= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f/go.mod h1:ZxIaCOlDmFupiL55aLU+Qp7O1dgwkDMBAQBK7wnEVBg= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -138,8 +136,7 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a534 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= @@ -290,8 +287,7 @@ github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnl github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 h1:4EYQaWAatQokdji3zqZloVIW/Ke1RQjYw2zHULyrHJg= github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= -github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -316,30 +312,18 @@ go.etcd.io/etcd/raft/v3 v3.5.10 h1:cgNAYe7xrsrn/5kXMSaH8kM/Ky8mAdMqGOxyYwpP0LA= go.etcd.io/etcd/raft/v3 v3.5.10/go.mod h1:odD6kr8XQXTy9oQnyMPBOr0TVe+gT0neQhElQ6jbGRc= go.etcd.io/etcd/server/v3 v3.5.10 h1:4NOGyOwD5sUZ22PiWYKmfxqoeh72z6EhYjNosKGLmZg= go.etcd.io/etcd/server/v3 v3.5.10/go.mod h1:gBplPHfs6YI0L+RpGkTQO7buDbHv5HJGG/Bst0/zIPo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0 h1:RtcvQ4iw3w9NBB5yRwgA4sSa82rfId7n4atVpvKx3bY= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0/go.mod h1:f/PbKbRd4cdUICWell6DmzvVJ7QrmBgFrRHjXmAXbK4= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/contrib/propagators/jaeger v1.22.0 h1:bAHX+zN/inu+Rbqk51REmC8oXLl+Dw6pp9ldQf/onaY= -go.opentelemetry.io/contrib/propagators/jaeger v1.22.0/go.mod h1:bH9GkgkN21mscXcQP6lQJYI8XnEPDxlTN/ZOBuHDjqE= -go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0 h1:Q9PrD94WoMolBx44ef5UWWvufpVSME0MiSymXZfedso= -go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0/go.mod h1:tjp49JHNvreAAoWjdCHIVD7NXMjuJ3Dp/9iNOuPPlC8= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.51.0 h1:974XTyIwHI4nHa1+uSLxHtUnlJ2DiVtAJjk7fd07p/8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= +go.opentelemetry.io/contrib/propagators/jaeger v1.26.0 h1:RH76Cl2pfOLLoCtxAPax9c7oYzuL1tiI7/ZPJEmEmOw= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.20.0 h1:ja+d7Aea/9PgGxB63+E0jtRFpma717wubS0KFkZpmYw= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 h1:Waw9Wfpo/IXzOI8bCB7DIk+0JZcqqsyn1JFnAc+iam8= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -432,7 +416,7 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index bde32825bee..86bbf410526 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -3,15 +3,15 @@ module github.com/grafana/grafana/pkg/promlib go 1.21.0 require ( - github.com/grafana/grafana-plugin-sdk-go v0.227.0 + github.com/grafana/grafana-plugin-sdk-go v0.228.0 github.com/json-iterator/go v1.1.12 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.19.0 github.com/prometheus/common v0.53.0 github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 github.com/stretchr/testify v1.9.0 - go.opentelemetry.io/otel v1.24.0 - go.opentelemetry.io/otel/trace v1.24.0 + go.opentelemetry.io/otel v1.26.0 + go.opentelemetry.io/otel/trace v1.26.0 golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb ) @@ -24,11 +24,11 @@ require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dennwc/varint v1.0.0 // indirect github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 // indirect @@ -55,7 +55,7 @@ require ( github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -89,18 +89,18 @@ require ( github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect github.com/unknwon/com v1.0.1 // indirect github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 // indirect - github.com/urfave/cli v1.22.14 // indirect + github.com/urfave/cli v1.22.15 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0 // indirect - go.opentelemetry.io/contrib/propagators/jaeger v1.22.0 // indirect - go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/sdk v1.24.0 // indirect - go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.51.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.26.0 // indirect + go.opentelemetry.io/contrib/samplers/jaegerremote v0.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 // indirect + go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/otel/sdk v1.26.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.3.0 // indirect golang.org/x/mod v0.15.0 // indirect @@ -110,7 +110,7 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.18.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index b0967562ab3..65973bdb9a4 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -16,17 +16,14 @@ github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZ github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 h1:aPflPkRFkVwbW6dmcVqfgwp1i+UWGFH6VgR1Jim5Ygc= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -89,13 +86,12 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/grafana-plugin-sdk-go v0.227.0 h1:xkARhSnCovkcDd0n8uwingJID4fAn8tKX7nR2M22ML8= +github.com/grafana/grafana-plugin-sdk-go v0.228.0 h1:LlPqyB+RZTtDy8RVYD7iQVJW5A0gMoGSI/+Ykz8HebQ= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= @@ -219,8 +215,7 @@ github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnl github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 h1:4EYQaWAatQokdji3zqZloVIW/Ke1RQjYw2zHULyrHJg= github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= -github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -229,30 +224,18 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0 h1:RtcvQ4iw3w9NBB5yRwgA4sSa82rfId7n4atVpvKx3bY= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0/go.mod h1:f/PbKbRd4cdUICWell6DmzvVJ7QrmBgFrRHjXmAXbK4= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/contrib/propagators/jaeger v1.22.0 h1:bAHX+zN/inu+Rbqk51REmC8oXLl+Dw6pp9ldQf/onaY= -go.opentelemetry.io/contrib/propagators/jaeger v1.22.0/go.mod h1:bH9GkgkN21mscXcQP6lQJYI8XnEPDxlTN/ZOBuHDjqE= -go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0 h1:Q9PrD94WoMolBx44ef5UWWvufpVSME0MiSymXZfedso= -go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0/go.mod h1:tjp49JHNvreAAoWjdCHIVD7NXMjuJ3Dp/9iNOuPPlC8= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.51.0 h1:974XTyIwHI4nHa1+uSLxHtUnlJ2DiVtAJjk7fd07p/8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= +go.opentelemetry.io/contrib/propagators/jaeger v1.26.0 h1:RH76Cl2pfOLLoCtxAPax9c7oYzuL1tiI7/ZPJEmEmOw= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.20.0 h1:ja+d7Aea/9PgGxB63+E0jtRFpma717wubS0KFkZpmYw= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 h1:Waw9Wfpo/IXzOI8bCB7DIk+0JZcqqsyn1JFnAc+iam8= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -307,7 +290,7 @@ golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSm golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= -google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= From 3d80693456b080dd913c8f26f98f2b3f41de4b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Tue, 30 Apr 2024 15:27:20 +0200 Subject: [PATCH 212/222] postgres: simpler test (#86331) --- .../sqleng/sql_engine_test.go | 47 ++++++++++--------- .../sqleng/util/util.go | 3 -- 2 files changed, 24 insertions(+), 26 deletions(-) delete mode 100644 pkg/tsdb/grafana-postgresql-datasource/sqleng/util/util.go diff --git a/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine_test.go b/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine_test.go index b7f422327ac..4d511a53f25 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine_test.go +++ b/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine_test.go @@ -13,9 +13,10 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana-plugin-sdk-go/backend/log" - "github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng/util" ) +func Pointer[T any](v T) *T { return &v } + func TestSQLEngine(t *testing.T) { dt := time.Date(2018, 3, 14, 21, 20, 6, int(527345*time.Microsecond), time.UTC) @@ -73,19 +74,19 @@ func TestSQLEngine(t *testing.T) { tSeconds, }), data.NewField("time2", nil, []*int64{ - util.Pointer(tSeconds), + Pointer(tSeconds), }), data.NewField("time3", nil, []int64{ tMilliseconds, }), data.NewField("time4", nil, []*int64{ - util.Pointer(tMilliseconds), + Pointer(tMilliseconds), }), data.NewField("time5", nil, []int64{ tNanoSeconds, }), data.NewField("time6", nil, []*int64{ - util.Pointer(tNanoSeconds), + Pointer(tNanoSeconds), }), data.NewField("time7", nil, []*int64{ nilPointer, @@ -117,19 +118,19 @@ func TestSQLEngine(t *testing.T) { tSeconds, }), data.NewField("time2", nil, []*uint64{ - util.Pointer(tSeconds), + Pointer(tSeconds), }), data.NewField("time3", nil, []uint64{ tMilliseconds, }), data.NewField("time4", nil, []*uint64{ - util.Pointer(tMilliseconds), + Pointer(tMilliseconds), }), data.NewField("time5", nil, []uint64{ tNanoSeconds, }), data.NewField("time6", nil, []*uint64{ - util.Pointer(tNanoSeconds), + Pointer(tNanoSeconds), }), data.NewField("time7", nil, []*uint64{ nilPointer, @@ -159,7 +160,7 @@ func TestSQLEngine(t *testing.T) { tSeconds, }), data.NewField("time2", nil, []*int32{ - util.Pointer(tSeconds), + Pointer(tSeconds), }), data.NewField("time7", nil, []*int32{ nilInt, @@ -184,7 +185,7 @@ func TestSQLEngine(t *testing.T) { tSeconds, }), data.NewField("time2", nil, []*uint32{ - util.Pointer(tSeconds), + Pointer(tSeconds), }), data.NewField("time7", nil, []*uint32{ nilInt, @@ -210,19 +211,19 @@ func TestSQLEngine(t *testing.T) { tSeconds, }), data.NewField("time2", nil, []*float64{ - util.Pointer(tSeconds), + Pointer(tSeconds), }), data.NewField("time3", nil, []float64{ tMilliseconds, }), data.NewField("time4", nil, []*float64{ - util.Pointer(tMilliseconds), + Pointer(tMilliseconds), }), data.NewField("time5", nil, []float64{ tNanoSeconds, }), data.NewField("time6", nil, []*float64{ - util.Pointer(tNanoSeconds), + Pointer(tNanoSeconds), }), data.NewField("time7", nil, []*float64{ nilPointer, @@ -252,7 +253,7 @@ func TestSQLEngine(t *testing.T) { tSeconds, }), data.NewField("time2", nil, []*float32{ - util.Pointer(tSeconds), + Pointer(tSeconds), }), data.NewField("time7", nil, []*float32{ nilInt, @@ -273,61 +274,61 @@ func TestSQLEngine(t *testing.T) { int64(1), }), data.NewField("value2", nil, []*int64{ - util.Pointer(int64(1)), + Pointer(int64(1)), }), data.NewField("value3", nil, []int32{ int32(1), }), data.NewField("value4", nil, []*int32{ - util.Pointer(int32(1)), + Pointer(int32(1)), }), data.NewField("value5", nil, []int16{ int16(1), }), data.NewField("value6", nil, []*int16{ - util.Pointer(int16(1)), + Pointer(int16(1)), }), data.NewField("value7", nil, []int8{ int8(1), }), data.NewField("value8", nil, []*int8{ - util.Pointer(int8(1)), + Pointer(int8(1)), }), data.NewField("value9", nil, []float64{ float64(1), }), data.NewField("value10", nil, []*float64{ - util.Pointer(1.0), + Pointer(1.0), }), data.NewField("value11", nil, []float32{ float32(1), }), data.NewField("value12", nil, []*float32{ - util.Pointer(float32(1)), + Pointer(float32(1)), }), data.NewField("value13", nil, []uint64{ uint64(1), }), data.NewField("value14", nil, []*uint64{ - util.Pointer(uint64(1)), + Pointer(uint64(1)), }), data.NewField("value15", nil, []uint32{ uint32(1), }), data.NewField("value16", nil, []*uint32{ - util.Pointer(uint32(1)), + Pointer(uint32(1)), }), data.NewField("value17", nil, []uint16{ uint16(1), }), data.NewField("value18", nil, []*uint16{ - util.Pointer(uint16(1)), + Pointer(uint16(1)), }), data.NewField("value19", nil, []uint8{ uint8(1), }), data.NewField("value20", nil, []*uint8{ - util.Pointer(uint8(1)), + Pointer(uint8(1)), }), ) for i := 0; i < len(originFrame.Fields); i++ { diff --git a/pkg/tsdb/grafana-postgresql-datasource/sqleng/util/util.go b/pkg/tsdb/grafana-postgresql-datasource/sqleng/util/util.go deleted file mode 100644 index f6b5d330c17..00000000000 --- a/pkg/tsdb/grafana-postgresql-datasource/sqleng/util/util.go +++ /dev/null @@ -1,3 +0,0 @@ -package util - -func Pointer[T any](v T) *T { return &v } From 4cc6b53a6dd46c00b5752369210371a8e23286df Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Tue, 30 Apr 2024 15:59:42 +0200 Subject: [PATCH 213/222] Chore: InfluxDB unit testing overhaul (#86586) * move mocks into the __mocks__ folder * refactor datasource.test.ts * refactor datasource_backend_mode.test.ts * add dbName tests * prettier * betterer --- .betterer.results | 6 - .../influxdb/__mocks__/datasource.ts | 89 +++ .../datasource/influxdb/__mocks__/query.ts | 32 + .../datasource/influxdb/__mocks__/request.ts | 99 +++ .../{mocks.ts => __mocks__/response.ts} | 278 +++----- .../visual/VisualInfluxQLEditor.test.tsx | 2 +- .../datasource/influxdb/datasource.test.ts | 615 +++++++++++------- .../plugins/datasource/influxdb/datasource.ts | 2 +- .../influxdb/datasource_backend_mode.test.ts | 443 ------------- .../influxdb/datasource_sql.test.ts | 10 +- .../fsql/datasource.flightsql.test.ts | 3 +- .../influxdb/influxql_metadata_query.test.ts | 2 +- .../influxdb/influxql_query_builder.test.ts | 2 +- .../influxdb/response_parser.test.ts | 2 +- 14 files changed, 687 insertions(+), 898 deletions(-) create mode 100644 public/app/plugins/datasource/influxdb/__mocks__/datasource.ts create mode 100644 public/app/plugins/datasource/influxdb/__mocks__/query.ts create mode 100644 public/app/plugins/datasource/influxdb/__mocks__/request.ts rename public/app/plugins/datasource/influxdb/{mocks.ts => __mocks__/response.ts} (56%) delete mode 100644 public/app/plugins/datasource/influxdb/datasource_backend_mode.test.ts diff --git a/.betterer.results b/.betterer.results index 2c05174492b..8509dde739a 100644 --- a/.betterer.results +++ b/.betterer.results @@ -5154,12 +5154,6 @@ exports[`better eslint`] = { "public/app/plugins/datasource/influxdb/migrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], - "public/app/plugins/datasource/influxdb/mocks.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Do not use any type assertions.", "3"] - ], "public/app/plugins/datasource/influxdb/query_part.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], diff --git a/public/app/plugins/datasource/influxdb/__mocks__/datasource.ts b/public/app/plugins/datasource/influxdb/__mocks__/datasource.ts new file mode 100644 index 00000000000..6630252bd64 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/__mocks__/datasource.ts @@ -0,0 +1,89 @@ +import { of } from 'rxjs'; + +import { AdHocVariableFilter, DataSourceInstanceSettings, PluginType, ScopedVars } from '@grafana/data'; +import { FetchResponse, getBackendSrv, setBackendSrv, VariableInterpolation } from '@grafana/runtime'; + +import { TemplateSrv } from '../../../../features/templating/template_srv'; +import InfluxDatasource from '../datasource'; +import { InfluxOptions, InfluxVersion } from '../types'; + +const getAdhocFiltersMock = jest.fn().mockImplementation(() => []); +const replaceMock = jest.fn().mockImplementation((a: string, ...rest: unknown[]) => a); + +export const templateSrvStub = { + getAdhocFilters: getAdhocFiltersMock, + replace: replaceMock, +} as unknown as TemplateSrv; + +export function mockTemplateSrv( + getAdhocFiltersMock: (datasourceName: string) => AdHocVariableFilter[], + replaceMock: ( + target?: string, + scopedVars?: ScopedVars, + format?: string | Function | undefined, + interpolations?: VariableInterpolation[] + ) => string +): TemplateSrv { + return { + getAdhocFilters: getAdhocFiltersMock, + replace: replaceMock, + } as unknown as TemplateSrv; +} + +export function mockBackendService(response: FetchResponse) { + const fetchMock = jest.fn().mockReturnValue(of(response)); + const origBackendSrv = getBackendSrv(); + setBackendSrv({ + ...origBackendSrv, + fetch: fetchMock, + }); + return fetchMock; +} + +export function getMockInfluxDS( + instanceSettings: DataSourceInstanceSettings = getMockDSInstanceSettings(), + templateSrv: TemplateSrv = templateSrvStub +): InfluxDatasource { + return new InfluxDatasource(instanceSettings, templateSrv); +} + +export function getMockDSInstanceSettings( + overrideJsonData?: Partial +): DataSourceInstanceSettings { + return { + id: 123, + url: 'proxied', + access: 'proxy', + name: 'influxDb', + readOnly: false, + uid: 'influxdb-test', + type: 'influxdb', + meta: { + id: 'influxdb-meta', + type: PluginType.datasource, + name: 'influxdb-test', + info: { + author: { + name: 'observability-metrics', + }, + version: 'v0.0.1', + description: 'test', + links: [], + logos: { + large: '', + small: '', + }, + updated: '', + screenshots: [], + }, + module: '', + baseUrl: '', + }, + jsonData: { + version: InfluxVersion.InfluxQL, + httpMode: 'POST', + dbName: 'site', + ...(overrideJsonData ? overrideJsonData : {}), + }, + }; +} diff --git a/public/app/plugins/datasource/influxdb/__mocks__/query.ts b/public/app/plugins/datasource/influxdb/__mocks__/query.ts new file mode 100644 index 00000000000..0d74ecf113e --- /dev/null +++ b/public/app/plugins/datasource/influxdb/__mocks__/query.ts @@ -0,0 +1,32 @@ +import { DataQueryRequest, dateTime } from '@grafana/data'; + +import { InfluxQuery } from '../types'; + +const now = dateTime('2023-09-16T21:26:00Z'); + +export const queryOptions: DataQueryRequest = { + app: 'dashboard', + interval: '10', + intervalMs: 10, + requestId: 'A-testing', + startTime: 0, + range: { + from: dateTime(now).subtract(15, 'minutes'), + to: now, + raw: { + from: 'now-15m', + to: 'now', + }, + }, + rangeRaw: { + from: 'now-15m', + to: 'now', + }, + targets: [], + timezone: 'UTC', + scopedVars: { + interval: { text: '1m', value: '1m' }, + __interval: { text: '1m', value: '1m' }, + __interval_ms: { text: 60000, value: 60000 }, + }, +}; diff --git a/public/app/plugins/datasource/influxdb/__mocks__/request.ts b/public/app/plugins/datasource/influxdb/__mocks__/request.ts new file mode 100644 index 00000000000..986c780922d --- /dev/null +++ b/public/app/plugins/datasource/influxdb/__mocks__/request.ts @@ -0,0 +1,99 @@ +import { AdHocVariableFilter, DataQueryRequest, dateTime } from '@grafana/data'; +import { SQLQuery } from '@grafana/sql'; + +import { InfluxQuery } from '../types'; + +type QueryType = InfluxQuery & SQLQuery; + +export const mockInfluxQueryRequest = (targets?: QueryType[]): DataQueryRequest => { + return { + app: 'explore', + interval: '1m', + intervalMs: 60000, + range: { + from: dateTime(0), + to: dateTime(10), + raw: { from: dateTime(0), to: dateTime(10) }, + }, + rangeRaw: { + from: dateTime(0), + to: dateTime(10), + }, + requestId: '', + scopedVars: {}, + startTime: 0, + targets: targets ?? mockTargets(), + timezone: '', + }; +}; + +export const mockTargets = (): QueryType[] => { + return [ + { + refId: 'A', + datasource: { + type: 'influxdb', + uid: 'vA4bkHenk', + }, + policy: 'default', + resultFormat: 'time_series', + orderByTime: 'ASC', + tags: [], + groupBy: [ + { + type: 'time', + params: ['$__interval'], + }, + { + type: 'fill', + params: ['null'], + }, + ], + select: [ + [ + { + type: 'field', + params: ['value'], + }, + { + type: 'mean', + params: [], + }, + ], + ], + measurement: 'cpu', + }, + ]; +}; + +export const mockInfluxQueryWithTemplateVars = (adhocFilters: AdHocVariableFilter[]): InfluxQuery => ({ + refId: 'x', + alias: '$var1', + measurement: '$var1', + policy: '$var1', + limit: '$var1', + slimit: '$var1', + tz: '$var1', + tags: [ + { + key: 'drive', + operator: '=~', + value: '/^$path$/', + }, + ], + groupBy: [ + { + params: ['$var1'], + type: 'tag', + }, + ], + select: [ + [ + { + params: ['$var1'], + type: 'field', + }, + ], + ], + adhocFilters, +}); diff --git a/public/app/plugins/datasource/influxdb/mocks.ts b/public/app/plugins/datasource/influxdb/__mocks__/response.ts similarity index 56% rename from public/app/plugins/datasource/influxdb/mocks.ts rename to public/app/plugins/datasource/influxdb/__mocks__/response.ts index 8aee8e79b5c..23b6cef2595 100644 --- a/public/app/plugins/datasource/influxdb/mocks.ts +++ b/public/app/plugins/datasource/influxdb/__mocks__/response.ts @@ -1,108 +1,5 @@ -import { of } from 'rxjs'; - -import { - AdHocVariableFilter, - DataQueryRequest, - DataSourceInstanceSettings, - dateTime, - FieldType, - PluginType, - ScopedVars, -} from '@grafana/data'; -import { - BackendDataSourceResponse, - FetchResponse, - getBackendSrv, - setBackendSrv, - VariableInterpolation, -} from '@grafana/runtime'; -import { SQLQuery } from '@grafana/sql'; - -import { TemplateSrv } from '../../../features/templating/template_srv'; - -import InfluxDatasource from './datasource'; -import { InfluxOptions, InfluxQuery, InfluxVersion } from './types'; - -const getAdhocFiltersMock = jest.fn().mockImplementation(() => []); -const replaceMock = jest.fn().mockImplementation((a: string, ...rest: unknown[]) => a); - -export const templateSrvStub = { - getAdhocFilters: getAdhocFiltersMock, - replace: replaceMock, -} as unknown as TemplateSrv; - -export function mockTemplateSrv( - getAdhocFiltersMock: (datasourceName: string) => AdHocVariableFilter[], - replaceMock: ( - target?: string, - scopedVars?: ScopedVars, - format?: string | Function | undefined, - interpolations?: VariableInterpolation[] - ) => string -): TemplateSrv { - return { - getAdhocFilters: getAdhocFiltersMock, - replace: replaceMock, - } as unknown as TemplateSrv; -} - -export function mockBackendService(response: FetchResponse) { - const fetchMock = jest.fn().mockReturnValue(of(response)); - const origBackendSrv = getBackendSrv(); - setBackendSrv({ - ...origBackendSrv, - fetch: fetchMock, - }); - return fetchMock; -} - -export function getMockInfluxDS( - instanceSettings: DataSourceInstanceSettings = getMockDSInstanceSettings(), - templateSrv: TemplateSrv = templateSrvStub -): InfluxDatasource { - return new InfluxDatasource(instanceSettings, templateSrv); -} - -export function getMockDSInstanceSettings( - overrideJsonData?: Partial -): DataSourceInstanceSettings { - return { - id: 123, - url: 'proxied', - access: 'proxy', - name: 'influxDb', - readOnly: false, - uid: 'influxdb-test', - type: 'influxdb', - meta: { - id: 'influxdb-meta', - type: PluginType.datasource, - name: 'influxdb-test', - info: { - author: { - name: 'observability-metrics', - }, - version: 'v0.0.1', - description: 'test', - links: [], - logos: { - large: '', - small: '', - }, - updated: '', - screenshots: [], - }, - module: '', - baseUrl: '', - }, - jsonData: { - version: InfluxVersion.InfluxQL, - httpMode: 'POST', - dbName: 'site', - ...(overrideJsonData ? overrideJsonData : {}), - }, - }; -} +import { FieldType } from '@grafana/data'; +import { BackendDataSourceResponse, FetchResponse } from '@grafana/runtime'; export const mockInfluxFetchResponse = ( overrides?: Partial> @@ -133,6 +30,7 @@ export const mockInfluxFetchResponse = ( ...overrides, }; }; + export const mockInfluxTSDBQueryResponse = [ { schema: { @@ -220,6 +118,33 @@ export const mockInfluxTSDBQueryResponse = [ }, ]; +export const metricFindQueryResponse = { + config: { + url: 'mock-response-url', + }, + headers: new Headers(), + ok: false, + redirected: false, + status: 0, + statusText: '', + type: 'basic', + url: '', + data: { + status: 'success', + results: [ + { + series: [ + { + name: 'measurement', + columns: ['name'], + values: [['cpu']], + }, + ], + }, + ], + }, +}; + export const mockInfluxRetentionPolicyResponse = [ { schema: { @@ -230,101 +155,6 @@ export const mockInfluxRetentionPolicyResponse = [ }, ]; -type QueryType = InfluxQuery & SQLQuery; - -export const mockInfluxQueryRequest = (targets?: QueryType[]): DataQueryRequest => { - return { - app: 'explore', - interval: '1m', - intervalMs: 60000, - range: { - from: dateTime(0), - to: dateTime(10), - raw: { from: dateTime(0), to: dateTime(10) }, - }, - rangeRaw: { - from: dateTime(0), - to: dateTime(10), - }, - requestId: '', - scopedVars: {}, - startTime: 0, - targets: targets ?? mockTargets(), - timezone: '', - }; -}; - -export const mockTargets = (): QueryType[] => { - return [ - { - refId: 'A', - datasource: { - type: 'influxdb', - uid: 'vA4bkHenk', - }, - policy: 'default', - resultFormat: 'time_series', - orderByTime: 'ASC', - tags: [], - groupBy: [ - { - type: 'time', - params: ['$__interval'], - }, - { - type: 'fill', - params: ['null'], - }, - ], - select: [ - [ - { - type: 'field', - params: ['value'], - }, - { - type: 'mean', - params: [], - }, - ], - ], - measurement: 'cpu', - }, - ]; -}; - -export const mockInfluxQueryWithTemplateVars = (adhocFilters: AdHocVariableFilter[]): InfluxQuery => ({ - refId: 'x', - alias: '$interpolationVar', - measurement: '$interpolationVar', - policy: '$interpolationVar', - limit: '$interpolationVar', - slimit: '$interpolationVar', - tz: '$interpolationVar', - tags: [ - { - key: 'cpu', - operator: '=~', - value: '/^$interpolationVar,$interpolationVar2$/', - }, - ], - groupBy: [ - { - params: ['$interpolationVar'], - type: 'tag', - }, - ], - select: [ - [ - { - params: ['$interpolationVar'], - type: 'field', - }, - ], - ], - adhocFilters, -}); - export const mockInfluxSQLFetchResponse: FetchResponse = { config: { url: 'mock-response-url', @@ -433,3 +263,51 @@ export const mockInfluxSQLVariableFetchResponse: FetchResponse { +describe('datasource initialization', () => { + it('should read the http method from jsonData', () => { + let ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' })); + expect(ds.httpMode).toBe('GET'); + ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' })); + expect(ds.httpMode).toBe('POST'); + }); +}); + +// Remove this suite when influxdbBackendMigration feature toggle removed +describe('InfluxDataSource Frontend Mode [influxdbBackendMigration=false]', () => { beforeEach(() => { + // we want only frontend mode in this suite + config.featureToggles.influxdbBackendMigration = false; jest.clearAllMocks(); }); - it('should throw an error if there is 200 response with error', async () => { - const ds = getMockInfluxDS(); - fetchMock.mockImplementation(() => { - return of({ - data: { - results: [ - { - error: 'Query timeout', - }, - ], - }, + describe('general checks', () => { + it('should throw an error if there is 200 response with error', async () => { + const ds = getMockInfluxDS(); + fetchMock.mockImplementation(() => { + return of({ + data: { + results: [ + { + error: 'Query timeout', + }, + ], + }, + }); }); + + try { + await lastValueFrom(ds.query(mockInfluxQueryRequest())); + } catch (err) { + if (err instanceof Error) { + expect(err.message).toBe('InfluxDB Error: Query timeout'); + } + } }); - try { - await lastValueFrom(ds.query(mockInfluxQueryRequest())); - } catch (err) { - if (err instanceof Error) { - expect(err.message).toBe('InfluxDB Error: Query timeout'); - } - } - }); - - describe('outdated browser mode', () => { - it('should throw an error when querying data', async () => { + it('should throw an error when querying data when deprecated access mode', async () => { expect.assertions(1); const instanceSettings = getMockDSInstanceSettings(); instanceSettings.access = 'direct'; @@ -68,7 +72,7 @@ describe('InfluxDataSource Frontend Mode', () => { }); }); - describe('metricFindQuery with HTTP GET', () => { + describe('metricFindQuery', () => { let ds: InfluxDatasource; const query = 'SELECT max(value) FROM measurement WHERE $timeFilter'; const queryOptions = { @@ -77,14 +81,7 @@ describe('InfluxDataSource Frontend Mode', () => { to: '2018-01-02T00:00:00Z', }, }; - - let requestQuery: string; - let requestMethod: string | undefined; - let requestData: string | null; const fetchMockImpl = (req: BackendSrvRequest) => { - requestMethod = req.method; - requestQuery = req.params?.q; - requestData = req.data; return of({ data: { status: 'success', @@ -108,36 +105,29 @@ describe('InfluxDataSource Frontend Mode', () => { fetchMock.mockImplementation(fetchMockImpl); }); - it('should read the http method from jsonData', async () => { - ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' })); - await ds.metricFindQuery(query, queryOptions); - expect(requestMethod).toBe('GET'); - ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' })); - await ds.metricFindQuery(query, queryOptions); - expect(requestMethod).toBe('POST'); - }); - it('should replace $timefilter', async () => { ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' })); await ds.metricFindQuery(query, queryOptions); - expect(requestQuery).toMatch('time >= 1514764800000ms and time <= 1514851200000ms'); + expect(fetchMock.mock.lastCall[0].params?.q).toMatch('time >= 1514764800000ms and time <= 1514851200000ms'); ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' })); await ds.metricFindQuery(query, queryOptions); - expect(requestQuery).toBeFalsy(); - expect(requestData).toMatch('time%20%3E%3D%201514764800000ms%20and%20time%20%3C%3D%201514851200000ms'); + expect(fetchMock.mock.lastCall[0].params?.q).toBeFalsy(); + expect(fetchMock.mock.lastCall[0].data).toMatch( + 'time%20%3E%3D%201514764800000ms%20and%20time%20%3C%3D%201514851200000ms' + ); }); it('should not have any data in request body if http mode is GET', async () => { ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' })); await ds.metricFindQuery(query, queryOptions); - expect(requestData).toBeNull(); + expect(fetchMock.mock.lastCall[0].data).toBeNull(); }); it('should have data in request body if http mode is POST', async () => { ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' })); await ds.metricFindQuery(query, queryOptions); - expect(requestData).not.toBeNull(); - expect(requestData).toMatch('q=SELECT'); + expect(fetchMock.mock.lastCall[0].data).not.toBeNull(); + expect(fetchMock.mock.lastCall[0].data).toMatch('q=SELECT'); }); it('parse response correctly', async () => { @@ -150,6 +140,7 @@ describe('InfluxDataSource Frontend Mode', () => { }); }); + // Update this after starting to use TemplateSrv from @grafana/runtime package describe('adhoc variables', () => { const adhocFilters = [ { @@ -163,8 +154,6 @@ describe('InfluxDataSource Frontend Mode', () => { mockTemplateService.getAdhocFilters = jest.fn((_: string) => adhocFilters); let ds = getMockInfluxDS(getMockDSInstanceSettings(), mockTemplateService); - // const fetchMock = jest.fn().mockReturnValue(fetchResult); - it('query should contain the ad-hoc variable', () => { ds.query(mockInfluxQueryRequest()); const expected = encodeURIComponent( @@ -252,105 +241,75 @@ describe('InfluxDataSource Frontend Mode', () => { ds.getTagValues({ key: 'test', filters: [] }); expect(metricFindQueryMock).toHaveBeenCalled(); }); + + it('should use dbName instead of database', () => { + const instanceSettings = getMockDSInstanceSettings(); + instanceSettings.database = 'should_not_be_used'; + ds = getMockInfluxDS(instanceSettings); + expect(ds.database).toBe('site'); + }); + + it('should fallback to use use database is dbName is not exist', () => { + const instanceSettings = getMockDSInstanceSettings(); + instanceSettings.database = 'fallback'; + instanceSettings.jsonData.dbName = undefined; + ds = getMockInfluxDS(instanceSettings); + expect(ds.database).toBe('fallback'); + }); }); describe('variable interpolation', () => { - const text = 'interpolationText'; - const text2 = 'interpolationText2'; - const textWithoutFormatRegex = 'interpolationText,interpolationText2'; - const textWithFormatRegex = 'interpolationText,interpolationText2'; - const justText = 'interpolationText'; - const variableMap: Record = { - $interpolationVar: text, - $interpolationVar2: text2, - }; - const adhocFilters = [ - { - key: 'adhoc', - operator: '=', - value: 'val', - condition: '', - }, + const variablesMock = [ + queryBuilder().withId('var1').withName('var1').withCurrent('var1_value').build(), + queryBuilder().withId('path').withName('path').withCurrent('/etc/hosts').build(), ]; - const templateSrv = mockTemplateSrv( - jest.fn((_: string) => adhocFilters), - jest.fn((target?: string, scopedVars?: ScopedVars, format?: string | Function): string => { - if (!format) { - return variableMap[target!] || ''; - } - if (format === 'regex') { - return textWithFormatRegex; - } - return textWithoutFormatRegex; - }) - ); - const ds = new InfluxDatasource(getMockDSInstanceSettings(), templateSrv); - - function influxChecks(query: InfluxQuery) { - expect(templateSrv.replace).toBeCalledTimes(12); - expect(query.alias).toBe(text); - expect(query.measurement).toBe(textWithFormatRegex); - expect(query.policy).toBe(justText); - expect(query.limit).toBe(justText); - expect(query.slimit).toBe(justText); - expect(query.tz).toBe(text); - expect(query.tags![0].value).toBe(textWithFormatRegex); - expect(query.groupBy![0].params![0]).toBe(justText); - expect(query.select![0][0].params![0]).toBe(justText); - expect(query.adhocFilters?.[0].key).toBe(adhocFilters[0].key); - } + const mockTemplateService = new TemplateSrv({ + getVariables: () => variablesMock, + getVariableWithName: (name: string) => variablesMock.filter((v) => v.name === name)[0], + getFilteredVariables: jest.fn(), + }); + // Remove this after start using TemplateSrv from @grafana/runtime + mockTemplateService.getAdhocFilters = jest.fn(); describe('when interpolating query variables for dashboard->explore', () => { it('should interpolate all variables with Flux mode', () => { - ds.version = InfluxVersion.Flux; + const ds = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.Flux }), mockTemplateService); const fluxQuery = { refId: 'x', - query: '$interpolationVar,$interpolationVar2', + query: 'some query with $var1 and $path', }; - const queries = ds.interpolateVariablesInQueries([fluxQuery], { - interpolationVar: { text: text, value: text }, - interpolationVar2: { text: text2, value: text2 }, - }); - expect(templateSrv.replace).toBeCalledTimes(1); - expect(queries[0].query).toBe(textWithFormatRegex); + const queries = ds.interpolateVariablesInQueries([fluxQuery], {}); + expect(queries[0].query).toBe('some query with var1_value and /etc/hosts'); }); it('should interpolate all variables with InfluxQL mode', () => { - ds.version = InfluxVersion.InfluxQL; - const queries = ds.interpolateVariablesInQueries([mockInfluxQueryWithTemplateVars(adhocFilters)], { - interpolationVar: { text: text, value: text }, - interpolationVar2: { text: text2, value: text2 }, - }); - influxChecks(queries[0]); + const ds = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.InfluxQL }), mockTemplateService); + const [query] = ds.interpolateVariablesInQueries([mockInfluxQueryWithTemplateVars([])], {}); + expect(query.alias).toBe('var1_value'); + expect(query.measurement).toBe('var1_value'); + expect(query.policy).toBe('var1_value'); + expect(query.limit).toBe('var1_value'); + expect(query.slimit).toBe('var1_value'); + expect(query.tz).toBe('var1_value'); + expect(query.tags![0].value).toBe(`/^\\/etc\\/hosts$/`); + expect(query.groupBy![0].params![0]).toBe('var1_value'); + expect(query.select![0][0].params![0]).toBe('var1_value'); }); }); - describe('when interpolating template variables', () => { + describe('applyTemplateVariables', () => { it('should apply all template variables with Flux mode', () => { - ds.version = InfluxVersion.Flux; + const ds = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.Flux }), mockTemplateService); const fluxQuery = { refId: 'x', - query: '$interpolationVar', + query: '$var1', }; - const query = ds.applyTemplateVariables(fluxQuery, { - interpolationVar: { - text: text, - value: text, - }, - }); - expect(templateSrv.replace).toBeCalledTimes(1); - expect(query.query).toBe(text); + const query = ds.applyTemplateVariables(fluxQuery, {}); + expect(query.query).toBe('var1_value'); }); }); describe('variable interpolation with chained variables with frontend mode', () => { - const variablesMock = [queryBuilder().withId('var1').withName('var1').withCurrent('var1').build()]; - const mockTemplateService = new TemplateSrv({ - getVariables: () => variablesMock, - getVariableWithName: (name: string) => variablesMock.filter((v) => v.name === name)[0], - getFilteredVariables: jest.fn(), - }); - mockTemplateService.getAdhocFilters = jest.fn((_: string) => []); let ds = getMockInfluxDS(getMockDSInstanceSettings(), mockTemplateService); const fetchMockImpl = () => of({ @@ -416,114 +375,298 @@ describe('InfluxDataSource Frontend Mode', () => { expect(qData).toBe(qe); }); }); + }); +}); - describe('interpolateQueryExpr', () => { - let ds = getMockInfluxDS(getMockDSInstanceSettings(), new TemplateSrv()); - it('should return the value as it is', () => { - const value = 'normalValue'; - const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build(); - const result = ds.interpolateQueryExpr(value, variableMock, 'my query $tempVar'); - const expectation = 'normalValue'; - expect(result).toBe(expectation); - }); +describe('InfluxDataSource Backend Mode [influxdbBackendMigration=true]', () => { + beforeEach(() => { + // we want only backend mode in this suite + config.featureToggles.influxdbBackendMigration = true; + jest.clearAllMocks(); + }); - it('should return the escaped value if the value wrapped in regex', () => { - const value = '/special/path'; - const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build(); - const result = ds.interpolateQueryExpr(value, variableMock, 'select that where path = /$tempVar/'); - const expectation = `\\/special\\/path`; - expect(result).toBe(expectation); - }); + describe('metric find query', () => { + let ds = getMockInfluxDS(getMockDSInstanceSettings()); + it('handles multiple frames', async () => { + const fetchMockImpl = () => { + return of(mockMetricFindQueryResponse); + }; - it('should return the escaped value if the value wrapped in regex 2', () => { - const value = '/special/path'; - const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build(); - const result = ds.interpolateQueryExpr(value, variableMock, 'select that where path = /^$tempVar$/'); - const expectation = `\\/special\\/path`; - expect(result).toBe(expectation); - }); + fetchMock.mockImplementation(fetchMockImpl); + const values = await ds.getTagValues({ key: 'test_id', filters: [] }); + expect(fetchMock).toHaveBeenCalled(); + expect(values.length).toBe(5); + expect(values[0].text).toBe('test-t2-1'); + }); + }); - it('should return the escaped value if the value wrapped in regex 3', () => { - const value = ['env', 'env2', 'env3']; - const variableMock = queryBuilder() - .withId('tempVar') - .withName('tempVar') - .withMulti(false) - .withIncludeAll(true) - .build(); - const result = ds.interpolateQueryExpr(value, variableMock, 'select from /^($tempVar)$/'); - const expectation = `(env|env2|env3)`; - expect(result).toBe(expectation); - }); - - it('should **not** return the escaped value if the value **is not** wrapped in regex', () => { - const value = '/special/path'; - const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build(); - const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`); - const expectation = `/special/path`; - expect(result).toBe(expectation); - }); - - it('should **not** return the escaped value if the value **is not** wrapped in regex 2', () => { - const value = '12.2'; - const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build(); - const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`); - const expectation = `12.2`; - expect(result).toBe(expectation); - }); - - it('should escape the value **always** if the variable is a multi-value variable', () => { - const value = [`/special/path`, `/some/other/path`]; - const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti().build(); - const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`); - const expectation = `(\\/special\\/path|\\/some\\/other\\/path)`; - expect(result).toBe(expectation); - }); - - it('should escape and join with the pipe even the variable is not multi-value', () => { - const variableMock = queryBuilder() - .withId('tempVar') - .withName('tempVar') - .withCurrent('All', '$__all') - .withMulti(false) - .withAllValue('') - .withIncludeAll() - .withOptions( + describe('variable interpolation with chained variables with backend mode', () => { + const variablesMock = [ + queryBuilder().withId('var1').withName('var1').withCurrent('var1').build(), + queryBuilder().withId('path').withName('path').withCurrent('/etc/hosts').build(), + queryBuilder() + .withId('field_var') + .withName('field_var') + .withMulti(true) + .withOptions( + { + text: `field_1`, + value: `field_1`, + }, + { + text: `field_2`, + value: `field_2`, + }, + { + text: `field_3`, + value: `field_3`, + } + ) + .withCurrent(['field_1', 'field_3']) + .build(), + ]; + const mockTemplateService = new TemplateSrv({ + getVariables: () => variablesMock, + getVariableWithName: (name: string) => variablesMock.filter((v) => v.name === name)[0], + getFilteredVariables: jest.fn(), + }); + mockTemplateService.getAdhocFilters = jest.fn((_: string) => []); + let ds = getMockInfluxDS(getMockDSInstanceSettings(), mockTemplateService); + const fetchMockImpl = () => + of({ + data: { + status: 'success', + results: [ { - text: 'All', - value: '$__all', + series: [ + { + name: 'measurement', + columns: ['name'], + values: [['cpu']], + }, + ], + }, + ], + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + fetchMock.mockImplementation(fetchMockImpl); + }); + + it('should render chained regex variables with floating point number', () => { + ds.metricFindQuery(`SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED`, { + ...queryOptions, + scopedVars: { maxSED: { text: '8.1', value: '8.1' } }, + }); + const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1`; + const qData = fetchMock.mock.calls[0][0].data.queries[0].query; + expect(qData).toBe(qe); + }); + + it('should render chained regex variables with URL', () => { + ds.metricFindQuery('SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^$var1$/', { + ...queryOptions, + scopedVars: { + var1: { + text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg', + value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg', + }, + }, + }); + const qe = `SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`; + expect(fetchMock).toHaveBeenCalled(); + const qData = fetchMock.mock.calls[0][0].data.queries[0].query; + expect(qData).toBe(qe); + }); + + it('should render chained regex variables with floating point number and url', () => { + ds.metricFindQuery( + 'SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED AND agent_url =~ /^$var1$/', + { + ...queryOptions, + scopedVars: { + var1: { + text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg', + value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg', + }, + maxSED: { text: '8.1', value: '8.1' }, + }, + } + ); + const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1 AND agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`; + const qData = fetchMock.mock.calls[0][0].data.queries[0].query; + expect(qData).toBe(qe); + }); + + it('should interpolate variable inside a regex pattern', () => { + const query: InfluxQuery = { + refId: 'A', + tags: [ + { + key: 'key', + operator: '=~', + value: '/^.*-$var1$/', + }, + ], + }; + const res = ds.applyVariables(query, {}); + const expected = `/^.*-var1$/`; + expect(res.tags?.[0].value).toEqual(expected); + }); + + it('should remove regex wrappers when operator is not a regex operator', () => { + const query: InfluxQuery = { + refId: 'A', + tags: [ + { + key: 'key', + operator: '=', + value: '/^$path$/', + }, + ], + }; + const res = ds.applyVariables(query, {}); + const expected = `/etc/hosts`; + expect(res.tags?.[0].value).toEqual(expected); + }); + + it('should interpolate field keys with given scopedVars', () => { + const query: InfluxQuery = { + refId: 'A', + tags: [ + { + key: 'key', + operator: '=', + value: 'value', + }, + ], + select: [ + [ + { + type: 'field', + params: ['$field_var'], }, { - text: `/special/path`, - value: `/special/path`, + type: 'mean', + params: [], }, - { - text: `/some/other/path`, - value: `/some/other/path`, - } - ) - .build(); - const value = [`/special/path`, `/some/other/path`]; - const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = /$tempVar/`); - const expectation = `(\\/special\\/path|\\/some\\/other\\/path)`; - expect(result).toBe(expectation); - }); - - it('should return floating point number as it is', () => { - const variableMock = queryBuilder() - .withId('tempVar') - .withName('tempVar') - .withMulti(false) - .withOptions({ - text: `1.0`, - value: `1.0`, - }) - .build(); - const value = `1.0`; - const result = ds.interpolateQueryExpr(value, variableMock, `select value / $tempVar from /^measurement$/`); - const expectation = `1.0`; - expect(result).toBe(expectation); - }); + ], + ], + }; + const res = ds.applyVariables(query, { field_var: { text: 'field_3', value: 'field_3' } }); + const expected = `field_3`; + expect(res.select?.[0][0].params?.[0]).toEqual(expected); }); }); }); + +describe('interpolateQueryExpr', () => { + let ds = getMockInfluxDS(getMockDSInstanceSettings(), new TemplateSrv()); + it('should return the value as it is', () => { + const value = 'normalValue'; + const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build(); + const result = ds.interpolateQueryExpr(value, variableMock, 'my query $tempVar'); + const expectation = 'normalValue'; + expect(result).toBe(expectation); + }); + + it('should return the escaped value if the value wrapped in regex', () => { + const value = '/special/path'; + const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build(); + const result = ds.interpolateQueryExpr(value, variableMock, 'select that where path = /$tempVar/'); + const expectation = `\\/special\\/path`; + expect(result).toBe(expectation); + }); + + it('should return the escaped value if the value wrapped in regex 2', () => { + const value = '/special/path'; + const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build(); + const result = ds.interpolateQueryExpr(value, variableMock, 'select that where path = /^$tempVar$/'); + const expectation = `\\/special\\/path`; + expect(result).toBe(expectation); + }); + + it('should return the escaped value if the value wrapped in regex 3', () => { + const value = ['env', 'env2', 'env3']; + const variableMock = queryBuilder() + .withId('tempVar') + .withName('tempVar') + .withMulti(false) + .withIncludeAll(true) + .build(); + const result = ds.interpolateQueryExpr(value, variableMock, 'select from /^($tempVar)$/'); + const expectation = `(env|env2|env3)`; + expect(result).toBe(expectation); + }); + + it('should **not** return the escaped value if the value **is not** wrapped in regex', () => { + const value = '/special/path'; + const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build(); + const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`); + const expectation = `/special/path`; + expect(result).toBe(expectation); + }); + + it('should **not** return the escaped value if the value **is not** wrapped in regex 2', () => { + const value = '12.2'; + const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build(); + const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`); + const expectation = `12.2`; + expect(result).toBe(expectation); + }); + + it('should escape the value **always** if the variable is a multi-value variable', () => { + const value = [`/special/path`, `/some/other/path`]; + const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti().build(); + const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`); + const expectation = `(\\/special\\/path|\\/some\\/other\\/path)`; + expect(result).toBe(expectation); + }); + + it('should escape and join with the pipe even the variable is not multi-value', () => { + const variableMock = queryBuilder() + .withId('tempVar') + .withName('tempVar') + .withCurrent('All', '$__all') + .withMulti(false) + .withAllValue('') + .withIncludeAll() + .withOptions( + { + text: 'All', + value: '$__all', + }, + { + text: `/special/path`, + value: `/special/path`, + }, + { + text: `/some/other/path`, + value: `/some/other/path`, + } + ) + .build(); + const value = [`/special/path`, `/some/other/path`]; + const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = /$tempVar/`); + const expectation = `(\\/special\\/path|\\/some\\/other\\/path)`; + expect(result).toBe(expectation); + }); + + it('should return floating point number as it is', () => { + const variableMock = queryBuilder() + .withId('tempVar') + .withName('tempVar') + .withMulti(false) + .withOptions({ + text: `1.0`, + value: `1.0`, + }) + .build(); + const value = `1.0`; + const result = ds.interpolateQueryExpr(value, variableMock, `select value / $tempVar from /^measurement$/`); + const expectation = `1.0`; + expect(result).toBe(expectation); + }); +}); diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index e54bde8734d..e1f514efa84 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -174,7 +174,7 @@ export default class InfluxDatasource extends DataSourceWithBackend { - const text = 'interpolationText'; - const text2 = 'interpolationText2'; - const textWithoutFormatRegex = 'interpolationText,interpolationText2'; - const textWithFormatRegex = 'interpolationText|interpolationText2'; - const variableMap: Record = { - $interpolationVar: text, - $interpolationVar2: text2, - }; - const adhocFilters = [ - { - key: 'adhoc', - operator: '=', - value: 'val', - condition: '', - }, - ]; - const templateSrv = mockTemplateSrv( - jest.fn(() => { - return adhocFilters; - }), - jest.fn((target?: string, scopedVars?: ScopedVars, format?: string | Function): string => { - if (!format) { - return variableMap[target!] || ''; - } - if (format === 'regex') { - return textWithFormatRegex; - } - return textWithoutFormatRegex; - }) - ); - - let queryOptions: DataQueryRequest; - let influxQuery: InfluxQuery; - const now = dateTime('2023-09-16T21:26:00Z'); - - beforeEach(() => { - queryOptions = { - app: 'dashboard', - interval: '10', - intervalMs: 10, - requestId: 'A-testing', - startTime: 0, - range: { - from: dateTime(now).subtract(15, 'minutes'), - to: now, - raw: { - from: 'now-15m', - to: 'now', - }, - }, - rangeRaw: { - from: 'now-15m', - to: 'now', - }, - targets: [], - timezone: 'UTC', - scopedVars: { - interval: { text: '1m', value: '1m' }, - __interval: { text: '1m', value: '1m' }, - __interval_ms: { text: 60000, value: 60000 }, - }, - }; - - influxQuery = { - refId: 'x', - alias: '$interpolationVar', - measurement: '$interpolationVar', - policy: '$interpolationVar', - limit: '$interpolationVar', - slimit: '$interpolationVar', - tz: '$interpolationVar', - tags: [ - { - key: 'cpu', - operator: '=~', - value: '/^$interpolationVar,$interpolationVar2$/', - }, - ], - groupBy: [ - { - params: ['$interpolationVar'], - type: 'tag', - }, - ], - select: [ - [ - { - params: ['$interpolationVar'], - type: 'field', - }, - ], - ], - }; - }); - - describe('adhoc filters', () => { - let fetchReq: { queries: InfluxQuery[] }; - const ctx = { - ds: getMockInfluxDS(getMockDSInstanceSettings(), templateSrv), - }; - beforeEach(async () => { - fetchMock.mockImplementation((req) => { - fetchReq = req.data; - return of(mockInfluxFetchResponse() as FetchResponse); - }); - const req = { - ...queryOptions, - targets: [...queryOptions.targets, { ...influxQuery, adhocFilters }], - }; - ctx.ds.query(req); - }); - - it('should add adhocFilters to the tags in the query', () => { - expect(fetchMock).toHaveBeenCalled(); - expect(fetchReq).not.toBeNull(); - expect(fetchReq.queries.length).toBe(1); - expect(fetchReq.queries[0].tags).toBeDefined(); - expect(fetchReq.queries[0].tags?.length).toBe(2); - expect(fetchReq.queries[0].tags?.[1].key).toBe(adhocFilters[0].key); - expect(fetchReq.queries[0].tags?.[1].value).toBe(adhocFilters[0].value); - }); - }); - - describe('when interpolating template variables', () => { - const text = 'interpolationText'; - const text2 = 'interpolationText2'; - const textWithoutFormatRegex = 'interpolationText,interpolationText2'; - const textWithFormatRegex = 'interpolationText,interpolationText2'; - const justText = 'interpolationText'; - const variableMap: Record = { - $interpolationVar: text, - $interpolationVar2: text2, - }; - const adhocFilters = [ - { - key: 'adhoc', - operator: '=', - value: 'val', - condition: '', - }, - ]; - const templateSrv = mockTemplateSrv( - jest.fn((_: string) => adhocFilters), - jest.fn((target?: string, scopedVars?: ScopedVars, format?: string | Function): string => { - if (!format) { - return variableMap[target!] || ''; - } - if (format === 'regex') { - return textWithFormatRegex; - } - return textWithoutFormatRegex; - }) - ); - const ds = new InfluxDatasource(getMockDSInstanceSettings(), templateSrv); - - function influxChecks(query: InfluxQuery) { - expect(templateSrv.replace).toBeCalledTimes(12); - expect(query.alias).toBe(text); - expect(query.measurement).toBe(textWithFormatRegex); - expect(query.policy).toBe(justText); - expect(query.limit).toBe(justText); - expect(query.slimit).toBe(justText); - expect(query.tz).toBe(text); - expect(query.tags![0].value).toBe(textWithFormatRegex); - expect(query.groupBy![0].params![0]).toBe(justText); - expect(query.select![0][0].params![0]).toBe(justText); - expect(query.adhocFilters?.[0].key).toBe(adhocFilters[0].key); - } - - it('should apply all template variables with InfluxQL mode', () => { - ds.version = ds.version = InfluxVersion.InfluxQL; - ds.access = 'proxy'; - const query = ds.applyTemplateVariables(mockInfluxQueryWithTemplateVars(adhocFilters), { - interpolationVar: { text: text, value: text }, - interpolationVar2: { text: 'interpolationText2', value: 'interpolationText2' }, - }); - influxChecks(query); - }); - - it('should apply all scopedVars to tags', () => { - ds.version = InfluxVersion.InfluxQL; - ds.access = 'proxy'; - const query = ds.applyTemplateVariables(mockInfluxQueryWithTemplateVars(adhocFilters), { - interpolationVar: { text: text, value: text }, - interpolationVar2: { text: 'interpolationText2', value: 'interpolationText2' }, - }); - if (!query.tags?.length) { - throw new Error('Tags are not defined'); - } - const value = query.tags[0].value; - const scopedVars = 'interpolationText,interpolationText2'; - expect(value).toBe(scopedVars); - }); - }); - - describe('variable interpolation with chained variables with backend mode', () => { - const variablesMock = [ - queryBuilder().withId('var1').withName('var1').withCurrent('var1').build(), - queryBuilder().withId('path').withName('path').withCurrent('/etc/hosts').build(), - queryBuilder() - .withId('field_var') - .withName('field_var') - .withMulti(true) - .withOptions( - { - text: `field_1`, - value: `field_1`, - }, - { - text: `field_2`, - value: `field_2`, - }, - { - text: `field_3`, - value: `field_3`, - } - ) - .withCurrent(['field_1', 'field_3']) - .build(), - ]; - const mockTemplateService = new TemplateSrv({ - getVariables: () => variablesMock, - getVariableWithName: (name: string) => variablesMock.filter((v) => v.name === name)[0], - getFilteredVariables: jest.fn(), - }); - mockTemplateService.getAdhocFilters = jest.fn((_: string) => []); - let ds = getMockInfluxDS(getMockDSInstanceSettings(), mockTemplateService); - const fetchMockImpl = () => - of({ - data: { - status: 'success', - results: [ - { - series: [ - { - name: 'measurement', - columns: ['name'], - values: [['cpu']], - }, - ], - }, - ], - }, - }); - - beforeEach(() => { - jest.clearAllMocks(); - fetchMock.mockImplementation(fetchMockImpl); - }); - - it('should render chained regex variables with floating point number', () => { - ds.metricFindQuery(`SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED`, { - ...queryOptions, - scopedVars: { maxSED: { text: '8.1', value: '8.1' } }, - }); - const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1`; - const qData = fetchMock.mock.calls[0][0].data.queries[0].query; - expect(qData).toBe(qe); - }); - - it('should render chained regex variables with URL', () => { - ds.metricFindQuery('SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^$var1$/', { - ...queryOptions, - scopedVars: { - var1: { - text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg', - value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg', - }, - }, - }); - const qe = `SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`; - expect(fetchMock).toHaveBeenCalled(); - const qData = fetchMock.mock.calls[0][0].data.queries[0].query; - expect(qData).toBe(qe); - }); - - it('should render chained regex variables with floating point number and url', () => { - ds.metricFindQuery( - 'SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED AND agent_url =~ /^$var1$/', - { - ...queryOptions, - scopedVars: { - var1: { - text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg', - value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg', - }, - maxSED: { text: '8.1', value: '8.1' }, - }, - } - ); - const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1 AND agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`; - const qData = fetchMock.mock.calls[0][0].data.queries[0].query; - expect(qData).toBe(qe); - }); - - it('should interpolate variable inside a regex pattern', () => { - const query: InfluxQuery = { - refId: 'A', - tags: [ - { - key: 'key', - operator: '=~', - value: '/^.*-$var1$/', - }, - ], - }; - const res = ds.applyVariables(query, {}); - const expected = `/^.*-var1$/`; - expect(res.tags?.[0].value).toEqual(expected); - }); - - it('should remove regex wrappers when operator is not a regex operator', () => { - const query: InfluxQuery = { - refId: 'A', - tags: [ - { - key: 'key', - operator: '=', - value: '/^$path$/', - }, - ], - }; - const res = ds.applyVariables(query, {}); - const expected = `/etc/hosts`; - expect(res.tags?.[0].value).toEqual(expected); - }); - - it('should interpolate field keys with given scopedVars', () => { - const query: InfluxQuery = { - refId: 'A', - tags: [ - { - key: 'key', - operator: '=', - value: 'value', - }, - ], - select: [ - [ - { - type: 'field', - params: ['$field_var'], - }, - { - type: 'mean', - params: [], - }, - ], - ], - }; - const res = ds.applyVariables(query, { field_var: { text: 'field_3', value: 'field_3' } }); - const expected = `field_3`; - expect(res.select?.[0][0].params?.[0]).toEqual(expected); - }); - }); - - describe('metric find query', () => { - let ds = getMockInfluxDS(getMockDSInstanceSettings()); - it('handles multiple frames', async () => { - const fetchMockImpl = () => { - return of(mockMetricFindQueryResponse); - }; - - fetchMock.mockImplementation(fetchMockImpl); - const values = await ds.getTagValues({ key: 'test_id', filters: [] }); - expect(fetchMock).toHaveBeenCalled(); - expect(values.length).toBe(5); - expect(values[0].text).toBe('test-t2-1'); - }); - }); -}); - -const mockMetricFindQueryResponse = { - data: { - results: { - metricFindQuery: { - status: 200, - frames: [ - { - schema: { - name: 'NoneNone', - refId: 'metricFindQuery', - fields: [ - { - name: 'Value', - type: 'string', - typeInfo: { - frame: 'string', - }, - }, - ], - }, - data: { - values: [['test-t2-1', 'test-t2-10']], - }, - }, - { - schema: { - name: 'some-other', - refId: 'metricFindQuery', - fields: [ - { - name: 'Value', - type: 'string', - typeInfo: { - frame: 'string', - }, - }, - ], - }, - data: { - values: [['test-t2-1', 'test-t2-10', 'test-t2-2', 'test-t2-3', 'test-t2-4']], - }, - }, - ], - }, - }, - }, -}; diff --git a/public/app/plugins/datasource/influxdb/datasource_sql.test.ts b/public/app/plugins/datasource/influxdb/datasource_sql.test.ts index 705b370a264..bc516995873 100644 --- a/public/app/plugins/datasource/influxdb/datasource_sql.test.ts +++ b/public/app/plugins/datasource/influxdb/datasource_sql.test.ts @@ -3,14 +3,10 @@ import { lastValueFrom } from 'rxjs'; import { SQLQuery } from '@grafana/sql'; import config from 'app/core/config'; +import { getMockDSInstanceSettings, mockBackendService, mockTemplateSrv } from './__mocks__/datasource'; +import { mockInfluxQueryRequest } from './__mocks__/request'; +import { mockInfluxSQLFetchResponse } from './__mocks__/response'; import InfluxDatasource from './datasource'; -import { - getMockDSInstanceSettings, - mockBackendService, - mockInfluxQueryRequest, - mockInfluxSQLFetchResponse, - mockTemplateSrv, -} from './mocks'; import { InfluxVersion } from './types'; config.featureToggles.influxdbBackendMigration = true; diff --git a/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts b/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts index 30b0855893a..bea4f7bfc98 100644 --- a/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts +++ b/public/app/plugins/datasource/influxdb/fsql/datasource.flightsql.test.ts @@ -1,6 +1,7 @@ import { TemplateSrv } from '@grafana/runtime'; -import { getMockDSInstanceSettings, mockBackendService, mockInfluxSQLVariableFetchResponse } from '../mocks'; +import { getMockDSInstanceSettings, mockBackendService } from '../__mocks__/datasource'; +import { mockInfluxSQLVariableFetchResponse } from '../__mocks__/response'; import { FlightSQLDatasource } from './datasource.flightsql'; diff --git a/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts b/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts index e27429beb93..897eff27c05 100644 --- a/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts +++ b/public/app/plugins/datasource/influxdb/influxql_metadata_query.test.ts @@ -1,7 +1,7 @@ import config from 'app/core/config'; +import { getMockInfluxDS } from './__mocks__/datasource'; import { getAllMeasurements, getAllPolicies, getFieldKeys, getTagKeys, getTagValues } from './influxql_metadata_query'; -import { getMockInfluxDS } from './mocks'; import { InfluxQuery } from './types'; describe('influx_metadata_query', () => { diff --git a/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts b/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts index d5c70c4c572..4e86911da25 100644 --- a/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts +++ b/public/app/plugins/datasource/influxdb/influxql_query_builder.test.ts @@ -1,5 +1,5 @@ +import { templateSrvStub as templateService } from './__mocks__/datasource'; import { buildMetadataQuery } from './influxql_query_builder'; -import { templateSrvStub as templateService } from './mocks'; import { DEFAULT_POLICY } from './types'; describe('influxql-query-builder', () => { diff --git a/public/app/plugins/datasource/influxdb/response_parser.test.ts b/public/app/plugins/datasource/influxdb/response_parser.test.ts index 4a606d605ea..e206dca77a8 100644 --- a/public/app/plugins/datasource/influxdb/response_parser.test.ts +++ b/public/app/plugins/datasource/influxdb/response_parser.test.ts @@ -6,8 +6,8 @@ import { FetchResponse } from '@grafana/runtime'; import config from 'app/core/config'; import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ +import { getMockDSInstanceSettings, getMockInfluxDS } from './__mocks__/datasource'; import InfluxQueryModel from './influx_query_model'; -import { getMockDSInstanceSettings, getMockInfluxDS } from './mocks'; import ResponseParser, { getSelectedParams } from './response_parser'; import { InfluxQuery } from './types'; From 5c89b8fe1266ef035d7bcc36e9030bad67f50478 Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Tue, 30 Apr 2024 16:18:03 +0200 Subject: [PATCH 214/222] gRPC Server: Make message size limits configurable. (#86982) * gRPC Server: Make message size limits configurable. * Fix mistake, don't add opts twice * Apply suggestions from code review Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com> --------- Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com> --- conf/defaults.ini | 6 ++++++ conf/sample.ini | 2 ++ pkg/services/grpcserver/service.go | 10 +++++++++- pkg/setting/setting.go | 12 ++++++++---- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index b3a459c7df1..401a65c6314 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -107,6 +107,12 @@ key_file = # this will log the request and response for each unary gRPC call enable_logging = false +# Maximum size of a message that can be received in bytes. If not set, uses the gRPC default (4MiB). +max_recv_msg_size = + +# Maximum size of a message that can be sent in bytes. If not set, uses the gRPC default (unlimited). +max_send_msg_size = + #################################### Database ############################ [database] # You can configure the database connection by specifying type, host, name, user and password diff --git a/conf/sample.ini b/conf/sample.ini index 2cfef5081a6..c9d5b89e50a 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -101,6 +101,8 @@ ;use_tls = false ;cert_file = ;key_file = +;max_recv_msg_size = +;max_send_msg_size = #################################### Database #################################### [database] diff --git a/pkg/services/grpcserver/service.go b/pkg/services/grpcserver/service.go index 4826ae948da..ae2f9650a46 100644 --- a/pkg/services/grpcserver/service.go +++ b/pkg/services/grpcserver/service.go @@ -85,12 +85,20 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, authe opts = append(opts, grpc.Creds(credentials.NewTLS(cfg.GRPCServerTLSConfig))) } + if s.cfg.GRPCServerMaxRecvMsgSize > 0 { + opts = append(opts, grpc.MaxRecvMsgSize(s.cfg.GRPCServerMaxRecvMsgSize)) + } + + if s.cfg.GRPCServerMaxSendMsgSize > 0 { + opts = append(opts, grpc.MaxSendMsgSize(s.cfg.GRPCServerMaxSendMsgSize)) + } + s.server = grpc.NewServer(opts...) return s, nil } func (s *gPRCServerService) Run(ctx context.Context) error { - s.logger.Info("Running GRPC server", "address", s.cfg.GRPCServerAddress, "network", s.cfg.GRPCServerNetwork, "tls", s.cfg.GRPCServerTLSConfig != nil) + s.logger.Info("Running GRPC server", "address", s.cfg.GRPCServerAddress, "network", s.cfg.GRPCServerNetwork, "tls", s.cfg.GRPCServerTLSConfig != nil, "max_recv_msg_size", s.cfg.GRPCServerMaxRecvMsgSize, "max_send_msg_size", s.cfg.GRPCServerMaxSendMsgSize) listener, err := net.Listen(s.cfg.GRPCServerNetwork, s.cfg.GRPCServerAddress) if err != nil { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 478d28afdcf..76406ffabec 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -470,10 +470,12 @@ type Cfg struct { RBACSingleOrganization bool // GRPC Server. - GRPCServerNetwork string - GRPCServerAddress string - GRPCServerTLSConfig *tls.Config - GRPCServerEnableLogging bool // log request and response of each unary gRPC call + GRPCServerNetwork string + GRPCServerAddress string + GRPCServerTLSConfig *tls.Config + GRPCServerEnableLogging bool // log request and response of each unary gRPC call + GRPCServerMaxRecvMsgSize int + GRPCServerMaxSendMsgSize int CustomResponseHeaders map[string]string @@ -1769,6 +1771,8 @@ func readGRPCServerSettings(cfg *Cfg, iniFile *ini.File) error { cfg.GRPCServerNetwork = valueAsString(server, "network", "tcp") cfg.GRPCServerAddress = valueAsString(server, "address", "") cfg.GRPCServerEnableLogging = server.Key("enable_logging").MustBool(false) + cfg.GRPCServerMaxRecvMsgSize = server.Key("max_recv_msg_size").MustInt(0) + cfg.GRPCServerMaxSendMsgSize = server.Key("max_send_msg_size").MustInt(0) switch cfg.GRPCServerNetwork { case "unix": if cfg.GRPCServerAddress != "" { From 53f94ac50dde7bc6c25f6a8254e85a2e8b1ae138 Mon Sep 17 00:00:00 2001 From: Aaron Godin Date: Tue, 30 Apr 2024 09:19:34 -0500 Subject: [PATCH 215/222] Apply plugin route ReqAction to ds_proxy authorization (#86466) * Apply plugin route ReqAction to ds_proxy authorization Co-authored-by: Eric Leijonmarck * fix: move ds_proxy route Evaluator out of plugins pkg * move DataSourceProxy route authorization to method --------- Co-authored-by: Eric Leijonmarck --- pkg/api/pluginproxy/ds_proxy.go | 23 +++++++++++++++++++---- pkg/api/pluginproxy/pluginproxy.go | 2 +- pkg/plugins/plugins.go | 4 ---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index 8037c79a123..ac50243cd1d 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -19,6 +19,7 @@ import ( glog "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/accesscontrol" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -304,10 +305,8 @@ func (proxy *DataSourceProxy) validateRequest() error { continue } - if route.ReqRole.IsValid() { - if !proxy.ctx.HasUserRole(route.ReqRole) { - return errors.New("plugin proxy route access denied") - } + if !proxy.hasAccessToRoute(route) { + return errors.New("plugin proxy route access denied") } proxy.matchedRoute = route @@ -330,6 +329,22 @@ func (proxy *DataSourceProxy) validateRequest() error { return nil } +func (proxy *DataSourceProxy) hasAccessToRoute(route *plugins.Route) bool { + useRBAC := proxy.features.IsEnabled(proxy.ctx.Req.Context(), featuremgmt.FlagAccessControlOnCall) && route.ReqAction != "" + if useRBAC { + routeEval := accesscontrol.EvalPermission(route.ReqAction) + ok := routeEval.Evaluate(proxy.ctx.GetPermissions()) + if !ok { + proxy.ctx.Logger.Debug("plugin route is covered by RBAC, user doesn't have access", "route", proxy.ctx.Req.URL.Path) + } + return ok + } + if route.ReqRole.IsValid() { + return proxy.ctx.HasUserRole(route.ReqRole) + } + return true +} + func (proxy *DataSourceProxy) logRequest() { if !proxy.cfg.DataProxyLogging { return diff --git a/pkg/api/pluginproxy/pluginproxy.go b/pkg/api/pluginproxy/pluginproxy.go index c61a6a284b9..5a959d97ab6 100644 --- a/pkg/api/pluginproxy/pluginproxy.go +++ b/pkg/api/pluginproxy/pluginproxy.go @@ -122,7 +122,7 @@ func (proxy *PluginProxy) HandleRequest() { } func (proxy *PluginProxy) hasAccessToRoute(route *plugins.Route) bool { - useRBAC := proxy.features.IsEnabled(proxy.ctx.Req.Context(), featuremgmt.FlagAccessControlOnCall) && route.RequiresRBACAction() + useRBAC := proxy.features.IsEnabled(proxy.ctx.Req.Context(), featuremgmt.FlagAccessControlOnCall) && route.ReqAction != "" if useRBAC { hasAccess := ac.HasAccess(proxy.accessControl, proxy.ctx)(ac.EvalPermission(route.ReqAction)) if !hasAccess { diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 1a045651277..3c281ee86bf 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -204,10 +204,6 @@ type Route struct { Body json.RawMessage `json:"body"` } -func (r *Route) RequiresRBACAction() bool { - return r.ReqAction != "" -} - // Header describes an HTTP header that is forwarded with // the proxied request for a plugin route type Header struct { From 99f2de08476d7bd1f7ca28b884b635a0a5750606 Mon Sep 17 00:00:00 2001 From: antonio <45235678+tonypowa@users.noreply.github.com> Date: Tue, 30 Apr 2024 16:34:06 +0200 Subject: [PATCH 216/222] alerting:config-notifications links (#87127) * alerting:config-notifications links * link fix * linked blog post to examples section --- .../templating-labels-annotations.md | 2 +- .../alerting/configure-notifications/_index.md | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/sources/alerting/alerting-rules/templating-labels-annotations.md b/docs/sources/alerting/alerting-rules/templating-labels-annotations.md index 5177d6dad6f..38f08f8084b 100644 --- a/docs/sources/alerting/alerting-rules/templating-labels-annotations.md +++ b/docs/sources/alerting/alerting-rules/templating-labels-annotations.md @@ -30,7 +30,7 @@ Each template is evaluated whenever the alert rule is evaluated, and is evaluate ## Examples -Rather than write a complete tutorial on text/template, the following examples attempt to show the most common use-cases we have seen for templates. You can use these examples verbatim, or adapt them as necessary for your use case. For more information on how to write text/template refer to the [text/template](https://pkg.go.dev/text/template) documentation. +The following examples attempt to show the most common use-cases we have seen for templates. You can use these examples verbatim, or adapt them as necessary for your use case. For more information on how to write text/template refer see [the beginner's guide to alert notification templates in Grafana](https://grafana.com/blog/2023/04/05/grafana-alerting-a-beginners-guide-to-templating-alert-notifications/). ### Print all labels, comma separated diff --git a/docs/sources/alerting/configure-notifications/_index.md b/docs/sources/alerting/configure-notifications/_index.md index f709efed8fa..996b5ee29af 100644 --- a/docs/sources/alerting/configure-notifications/_index.md +++ b/docs/sources/alerting/configure-notifications/_index.md @@ -19,8 +19,19 @@ weight: 125 Choose how, when, and where to send your alert notifications. -As a first step, define your contact points; where to send your alert notifications to. A contact point is a set of one or more integrations that are used to deliver notifications. +As a first step, define your [contact points][contact-points] where to send your alert notifications to. A contact point is a set of one or more integrations that are used to deliver notifications. -Next, create a notification policy which is a set of rules for where, when and how your alerts are routed to contact points. In a notification policy, you define where to send your alert notifications by choosing one of the contact points you created. +Next, create a [notification policy][notification-policies] which is a set of rules for where, when and how your alerts are routed to contact points. In a notification policy, you define where to send your alert notifications by choosing one of the contact points you created. -Optionally, you can add notification templates to contact points for reuse and consistent messaging in your notifications. +Optionally, you can add [notification templates][templates-page] to contact points for reuse and consistent messaging in your notifications. + +{{% docs/reference %}} +[notification-policies]: "/docs/grafana/ -> /docs/grafana//alerting/fundamentals/notifications/notification-policies" +[notification-policies]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/notifications/notification-policies" + +[contact-points]: "/docs/grafana/ -> /docs/grafana//alerting/fundamentals/notifications/contact-points" +[contact-points]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/notifications/contact-points" + +[templates-page]: "/docs/grafana/ -> /docs/grafana//alerting/fundamentals/notifications/templates/" +[templates-page]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/notifications/templates/" +{{% /docs/reference %}} From 1ddbcc3f61523fca838b7663a65f5d8bd1a173b8 Mon Sep 17 00:00:00 2001 From: antonio <45235678+tonypowa@users.noreply.github.com> Date: Tue, 30 Apr 2024 16:34:18 +0200 Subject: [PATCH 217/222] alerting/notifications: links (#87122) * added links to each section * ref fix * ref fix2 * link fix --- .../fundamentals/notifications/_index.md | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/sources/alerting/fundamentals/notifications/_index.md b/docs/sources/alerting/fundamentals/notifications/_index.md index f56feb78679..9e4a7affb0d 100644 --- a/docs/sources/alerting/fundamentals/notifications/_index.md +++ b/docs/sources/alerting/fundamentals/notifications/_index.md @@ -26,17 +26,17 @@ Next, create a notification policy which is a set of rules for where, when and h ## Alertmanagers -Grafana uses Alertmanagers to send notifications for firing and resolved alerts. Grafana has its own Alertmanager, referred to as "Grafana" in the user interface, but also supports sending notifications from other Alertmanagers too, such as the [Prometheus Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/). The Grafana Alertmanager uses notification policies and contact points to configure how and where a notification is sent; how often a notification should be sent; and whether alerts should all be sent in the same notification, sent in grouped notifications based on a set of labels, or as separate notifications. +Grafana uses [Alertmanagers](https://grafana.com/docs/grafana/latest/alerting/fundamentals/alertmanager/) to send notifications for firing and resolved alerts. Grafana has its own Alertmanager, referred to as "Grafana" in the user interface, but also supports sending notifications from other Alertmanagers too, such as the [Prometheus Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/). The Grafana Alertmanager uses notification policies and contact points to configure how and where a notification is sent; how often a notification should be sent; and whether alerts should all be sent in the same notification, sent in grouped notifications based on a set of labels, or as separate notifications. ## Contact points -Contact points contain the configuration for sending alert notifications, specifying destinations like email, Slack, OnCall, webhooks, and their notification messages. They allow the customization of notification messages and the use of notification templates. +[Contact points][contact-points] contain the configuration for sending alert notifications, specifying destinations like email, Slack, OnCall, webhooks, and their notification messages. They allow the customization of notification messages and the use of notification templates. A contact point is a list of integrations, each sending a message to a specific destination. You can configure them via notification policies or alert rules. ## Notification policies -Notification policies control when and where notifications are sent. A notification policy can choose to send all alerts together in the same notification, send alerts in grouped notifications based on a set of labels, or send alerts as separate notifications. You can configure each notification policy to control how often notifications should be sent as well as having one or more mute timings to inhibit notifications at certain times of the day and on certain days of the week. +[Notification policies][notification-policies] control when and where notifications are sent. A notification policy can choose to send all alerts together in the same notification, send alerts in grouped notifications based on a set of labels, or send alerts as separate notifications. You can configure each notification policy to control how often notifications should be sent as well as having one or more mute timings to inhibit notifications at certain times of the day and on certain days of the week. Notification policies are organized in a tree structure where at the root of the tree there is a notification policy called the default policy. There can be only one default policy and the default policy cannot be deleted. @@ -50,7 +50,7 @@ All alerts, irrespective of their labels, match the default policy. However, whe ## Notification templates -You can customize notifications with templates. For example, templates can be used to change the subject and message of an email, or the title and message of notifications sent to Slack. +You can customize notifications with [templates][templates-page]. For example, templates can be used to change the subject and message of an email, or the title and message of notifications sent to Slack. Templates are not limited to an individual integration or contact point, but instead can be used in a number of integrations in the same contact point and even integrations across different contact points. For example, a Grafana user can create a template called `custom_subject_or_title` and use it for both templating subjects in emails and titles of Slack messages without having to create two separate templates. @@ -58,4 +58,15 @@ All notifications templates are written in [Go's templating language](https://pk ## Silences -You can use silences to mute notifications from one or more firing rules. Silences do not stop alerts from firing or being resolved, or hide firing alerts in the user interface. A silence lasts as long as its duration, which can be configured in minutes, hours, days, months, or years. +You can use [silences](https://grafana.com/docs/grafana/latest/alerting/manage-notifications/create-silence/) to mute notifications from one or more firing rules. Silences do not stop alerts from firing or being resolved, or hide firing alerts in the user interface. A silence lasts as long as its duration, which can be configured in minutes, hours, days, months, or years. + +{{% docs/reference %}} +[notification-policies]: "/docs/grafana/ -> /docs/grafana//alerting/fundamentals/notifications/notification-policies" +[notification-policies]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/notifications/notification-policies" + +[contact-points]: "/docs/grafana/ -> /docs/grafana//alerting/fundamentals/notifications/contact-points" +[contact-points]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/notifications/contact-points" + +[templates-page]: "/docs/grafana/ -> /docs/grafana//alerting/fundamentals/notifications/templates/" +[templates-page]: "/docs/grafana-cloud/ -> /docs/grafana-cloud/alerting-and-irm/alerting/fundamentals/notifications/templates/" +{{% /docs/reference %}} From 86aceb7a1019e9965ddec19e926e805ca931e6c7 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Tue, 30 Apr 2024 16:58:25 +0200 Subject: [PATCH 218/222] Saga-icons: Forward SVG attributes (#87138) --- packages/grafana-icons/src/IconBase.tsx | 2 -- packages/grafana-icons/templates/icon.cjs | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/grafana-icons/src/IconBase.tsx b/packages/grafana-icons/src/IconBase.tsx index cc4149e7413..5c3983f0442 100644 --- a/packages/grafana-icons/src/IconBase.tsx +++ b/packages/grafana-icons/src/IconBase.tsx @@ -33,8 +33,6 @@ export const IconBase = ({ title, size = 'md', color = 'currentColor', ...props return ( { jsx.openingElement.name.name = 'IconBase'; jsx.openingElement.attributes = [ + ...jsx.openingElement.attributes, { type: 'JSXSpreadAttribute', argument: { From 5e060d2d99e1995d4ee4af1453a0c250f47eb414 Mon Sep 17 00:00:00 2001 From: Ieva Date: Tue, 30 Apr 2024 16:05:30 +0100 Subject: [PATCH 219/222] Data source: Maintain the default data source permissions when switching from unlicensed to licensed Grafana (#87119) set managed data source permissions upon resource creation for unlicensed Grafana, remove them on deletion --- .../ossaccesscontrol/permissions_services.go | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go b/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go index 693efe7053f..5101c049f36 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" + "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/libraryelements" @@ -280,13 +281,24 @@ func ProvideFolderPermissions( return &FolderPermissionsService{srv}, nil } -func ProvideDatasourcePermissionsService() *DatasourcePermissionsService { - return &DatasourcePermissionsService{} +// DatasourceQueryActions contains permissions to read information +// about a data source and submit arbitrary queries to it. +var DatasourceQueryActions = []string{ + datasources.ActionRead, + datasources.ActionQuery, +} + +func ProvideDatasourcePermissionsService(features featuremgmt.FeatureToggles, db db.DB, actionSetService resourcepermissions.ActionSetService) *DatasourcePermissionsService { + return &DatasourcePermissionsService{ + store: resourcepermissions.NewStore(db, features, &actionSetService), + } } var _ accesscontrol.DatasourcePermissionsService = new(DatasourcePermissionsService) -type DatasourcePermissionsService struct{} +type DatasourcePermissionsService struct { + store resourcepermissions.Store +} func (e DatasourcePermissionsService) GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]accesscontrol.ResourcePermission, error) { return nil, nil @@ -304,12 +316,39 @@ func (e DatasourcePermissionsService) SetBuiltInRolePermission(ctx context.Conte return nil, nil } +// SetPermissions sets managed permissions for a datasource in OSS. This ensures that Viewers and Editors maintain query access to a data source +// if an OSS/unlicensed instance is upgraded to Enterprise/licensed. +// https://github.com/grafana/identity-access-team/issues/672 func (e DatasourcePermissionsService) SetPermissions(ctx context.Context, orgID int64, resourceID string, commands ...accesscontrol.SetResourcePermissionCommand) ([]accesscontrol.ResourcePermission, error) { - return nil, nil + var dbCommands []resourcepermissions.SetResourcePermissionsCommand + for _, cmd := range commands { + // Only set query permissions for built-in roles + if cmd.Permission != "Query" || cmd.BuiltinRole == "" { + continue + } + actions := DatasourceQueryActions + + dbCommands = append(dbCommands, resourcepermissions.SetResourcePermissionsCommand{ + BuiltinRole: cmd.BuiltinRole, + SetResourcePermissionCommand: resourcepermissions.SetResourcePermissionCommand{ + Actions: actions, + Resource: datasources.ScopeRoot, + ResourceID: resourceID, + ResourceAttribute: "uid", + Permission: cmd.Permission, + }, + }) + } + + return e.store.SetResourcePermissions(ctx, orgID, dbCommands, resourcepermissions.ResourceHooks{}) } func (e DatasourcePermissionsService) DeleteResourcePermissions(ctx context.Context, orgID int64, resourceID string) error { - return nil + return e.store.DeleteResourcePermissions(ctx, orgID, &resourcepermissions.DeleteResourcePermissionsCmd{ + Resource: datasources.ScopeRoot, + ResourceAttribute: "uid", + ResourceID: resourceID, + }) } func (e DatasourcePermissionsService) MapActions(permission accesscontrol.ResourcePermission) string { From 93519f70ca166913d01f46cfa1065d4ca88260d8 Mon Sep 17 00:00:00 2001 From: William Wernert Date: Tue, 30 Apr 2024 11:14:01 -0400 Subject: [PATCH 220/222] Alerting: Also fix HCL field name for MuteTimeIntervals (#87079) * Correct HCL field name for MuteTimeIntervals * Update test --- pkg/services/ngalert/api/api_provisioning_test.go | 12 ++++++------ .../api/test-data/post-rulegroup-101-export.hcl | 12 ++++++------ .../definitions/provisioning_alert_rules.go | 14 ++++++++------ 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index 0d62ee98ed3..27eeb9e9da9 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -694,12 +694,12 @@ func TestProvisioningApi(t *testing.T) { is_paused = false notification_settings { - contact_point = "Test-Receiver" - group_by = ["alertname", "grafana_folder", "test"] - group_wait = "1s" - group_interval = "5s" - repeat_interval = "5m" - mute_time_intervals = ["test-mute"] + contact_point = "Test-Receiver" + group_by = ["alertname", "grafana_folder", "test"] + group_wait = "1s" + group_interval = "5s" + repeat_interval = "5m" + mute_timings = ["test-mute"] } } } diff --git a/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl b/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl index 19810a71554..142aead24d0 100644 --- a/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl +++ b/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl @@ -79,12 +79,12 @@ resource "grafana_rule_group" "rule_group_0000" { is_paused = false notification_settings { - contact_point = "Test-Receiver" - group_by = ["alertname", "grafana_folder", "test"] - group_wait = "1s" - group_interval = "5s" - repeat_interval = "5m" - mute_time_intervals = ["test-mute"] + contact_point = "Test-Receiver" + group_by = ["alertname", "grafana_folder", "test"] + group_wait = "1s" + group_interval = "5s" + repeat_interval = "5m" + mute_timings = ["test-mute"] } } } diff --git a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go index 0ebd6384a73..b25b41c3395 100644 --- a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go +++ b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go @@ -291,13 +291,15 @@ type RelativeTimeRangeExport struct { } // AlertRuleNotificationSettingsExport is the provisioned export of models.NotificationSettings. +// Field name mismatches with Terraform provider schema are noted where applicable. type AlertRuleNotificationSettingsExport struct { - // Terraform provider uses `contact_point`, so export the field with that name in HCL. + // TF -> `contact_point` Receiver string `yaml:"receiver,omitempty" json:"receiver,omitempty" hcl:"contact_point"` - GroupBy []string `yaml:"group_by,omitempty" json:"group_by,omitempty" hcl:"group_by"` - GroupWait *string `yaml:"group_wait,omitempty" json:"group_wait,omitempty" hcl:"group_wait,optional"` - GroupInterval *string `yaml:"group_interval,omitempty" json:"group_interval,omitempty" hcl:"group_interval,optional"` - RepeatInterval *string `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty" hcl:"repeat_interval,optional"` - MuteTimeIntervals []string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty" hcl:"mute_time_intervals"` + GroupBy []string `yaml:"group_by,omitempty" json:"group_by,omitempty" hcl:"group_by"` + GroupWait *string `yaml:"group_wait,omitempty" json:"group_wait,omitempty" hcl:"group_wait,optional"` + GroupInterval *string `yaml:"group_interval,omitempty" json:"group_interval,omitempty" hcl:"group_interval,optional"` + RepeatInterval *string `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty" hcl:"repeat_interval,optional"` + // TF -> `mute_timings` + MuteTimeIntervals []string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty" hcl:"mute_timings"` } From b0f6913ef6d5f8af51bc2d9e87634c1b44caae36 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Tue, 30 Apr 2024 16:17:55 +0100 Subject: [PATCH 221/222] Alerting: unify alert rule actions between list and detail view (#86071) * Add mock method for getting a plugin * Update tests to find "more" button via label * Remove test for Silence action in rule details * Unify alert rule actions to pull from same place * Restore behaviour of only showing incident button when firing * Fix identifier and pause permission/logic * Remove TODO comment related to refactor * Update snapshot for useAbilities * Undo optional param * Rename alert rule menu hook to component * Refactor hook to component * Rename Rule action buttons component * Chore: update style syntax for RuleDetails * Add tests for refactored alert rule menu * Only re-fetch Grafana managed alerts after pausing/resuming * Remove console log and check for extensions * Improve share rule generation of GMA rules * Rename component * Update action * Refactor plugins and fix tests * lint --------- Co-authored-by: Konrad Lalik Co-authored-by: Gilles De Mey --- .betterer.results | 12 - .../alerting/unified/RuleList.test.tsx | 2 +- public/app/features/alerting/unified/TODO.md | 1 - .../components/rule-viewer/Actions.tsx | 131 -------- .../components/rule-viewer/AlertRuleMenu.tsx | 139 ++++++++ .../rule-viewer/RuleViewer.test.tsx | 6 +- .../components/rule-viewer/RuleViewer.tsx | 12 +- .../rules/RuleActionsButtons.test.tsx | 126 ++++++++ .../components/rules/RuleActionsButtons.tsx | 299 ++++++------------ .../components/rules/RuleDetails.test.tsx | 28 -- .../unified/components/rules/RuleDetails.tsx | 36 +-- .../rules/RuleDetailsActionButtons.tsx | 257 --------------- .../components/rules/RuleDetailsButtons.tsx | 127 ++++++++ .../components/rules/RulesTable.test.tsx | 11 +- .../unified/components/rules/RulesTable.tsx | 2 +- .../RuleActionsButtons.test.tsx.snap | 28 ++ .../__snapshots__/useAbilities.test.tsx.snap | 8 + .../alerting/unified/hooks/useAbilities.ts | 2 + public/app/features/alerting/unified/mocks.ts | 57 ++-- .../alerting/unified/mocks/folders.ts | 6 + .../alerting/unified/mocks/plugins.ts | 14 +- .../alerting/unified/mocks/server/handlers.ts | 6 + .../alerting/unified/testSetup/plugins.ts | 19 +- .../features/alerting/unified/utils/misc.ts | 8 +- 24 files changed, 608 insertions(+), 729 deletions(-) delete mode 100644 public/app/features/alerting/unified/components/rule-viewer/Actions.tsx create mode 100644 public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx create mode 100644 public/app/features/alerting/unified/components/rules/RuleActionsButtons.test.tsx delete mode 100644 public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx create mode 100644 public/app/features/alerting/unified/components/rules/RuleDetailsButtons.tsx create mode 100644 public/app/features/alerting/unified/components/rules/__snapshots__/RuleActionsButtons.test.tsx.snap create mode 100644 public/app/features/alerting/unified/mocks/folders.ts diff --git a/.betterer.results b/.betterer.results index 8509dde739a..a9aa3e106a9 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2011,22 +2011,10 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "5"], [0, 0, 0, "Styles should be written using objects.", "6"] ], - "public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], "public/app/features/alerting/unified/components/rules/RuleConfigStatus.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] ], - "public/app/features/alerting/unified/components/rules/RuleDetails.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] - ], - "public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], "public/app/features/alerting/unified/components/rules/RuleDetailsAnnotations.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 7cac56a2518..85f00b549ff 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -155,7 +155,7 @@ const ui = { paused: byText(/^Paused/), }, actionButtons: { - more: byRole('button', { name: 'More' }), + more: byRole('button', { name: /more-actions/ }), }, moreActionItems: { pause: byRole('menuitem', { name: /pause evaluation/i }), diff --git a/public/app/features/alerting/unified/TODO.md b/public/app/features/alerting/unified/TODO.md index 86e60811548..749ed2e388b 100644 --- a/public/app/features/alerting/unified/TODO.md +++ b/public/app/features/alerting/unified/TODO.md @@ -17,7 +17,6 @@ If the item needs more rationale and you feel like a single sentence is inedequa ## Refactoring - Get rid of "+ Add new" in drop-downs : Let's see if is there a way we can make it work with `` -- There is a lot of overlap between `RuleActionButtons` and `RuleDetailsActionButtons`. As these components contain a lot of logic it would be nice to extract that logic into hooks - Create a shared timings form that can be used in both `EditDefaultPolicyForm.tsx` and `EditNotificationPolicyForm.tsx` ## Testing diff --git a/public/app/features/alerting/unified/components/rule-viewer/Actions.tsx b/public/app/features/alerting/unified/components/rule-viewer/Actions.tsx deleted file mode 100644 index ff9d296d417..00000000000 --- a/public/app/features/alerting/unified/components/rule-viewer/Actions.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import React from 'react'; - -import { AppEvents } from '@grafana/data'; -import { Dropdown, LinkButton, Menu } from '@grafana/ui'; -import appEvents from 'app/core/app_events'; -import MenuItemPauseRule from 'app/features/alerting/unified/components/MenuItemPauseRule'; -import { CombinedRule, RuleIdentifier } from 'app/types/unified-alerting'; - -import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities'; -import { useRulePluginLinkExtension } from '../../plugins/useRulePluginLinkExtensions'; -import { createShareLink, isLocalDevEnv, isOpenSourceEdition, makeRuleBasedSilenceLink } from '../../utils/misc'; -import * as ruleId from '../../utils/rule-id'; -import { createUrl } from '../../utils/url'; -import MoreButton from '../MoreButton'; -import { DeclareIncidentMenuItem } from '../bridges/DeclareIncidentButton'; - -import { useAlertRule } from './RuleContext'; - -interface Props { - handleDelete: (rule: CombinedRule) => void; - handleDuplicateRule: (identifier: RuleIdentifier) => void; -} - -export const useAlertRulePageActions = ({ handleDelete, handleDuplicateRule }: Props) => { - const { rule, identifier } = useAlertRule(); - const rulePluginLinkExtension = useRulePluginLinkExtension(rule); - - // check all abilities and permissions - const [editSupported, editAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Update); - const canEdit = editSupported && editAllowed; - - const [deleteSupported, deleteAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Delete); - const canDelete = deleteSupported && deleteAllowed; - - const [duplicateSupported, duplicateAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Duplicate); - const canDuplicate = duplicateSupported && duplicateAllowed; - - const [silenceSupported, silenceAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Silence); - const canSilence = silenceSupported && silenceAllowed; - - const [exportSupported, exportAllowed] = useAlertRuleAbility(rule, AlertRuleAction.ModifyExport); - const canExport = exportSupported && exportAllowed; - - /** - * Since Incident isn't available as an open-source product we shouldn't show it for Open-Source licenced editions of Grafana. - * We should show it in development mode - */ - const shouldShowDeclareIncidentButton = !isOpenSourceEdition() || isLocalDevEnv(); - const shareUrl = createShareLink(rule.namespace.rulesSource, rule); - - return [ - canEdit && , - - {canEdit && } - {canSilence && ( - - )} - {shouldShowDeclareIncidentButton && } - {canDuplicate && handleDuplicateRule(identifier)} />} - - copyToClipboard(shareUrl)} /> - {canExport && ( - ]} - /> - )} - {rulePluginLinkExtension.length > 0 && ( - <> - - {rulePluginLinkExtension.map((extension) => ( - - ))} - - )} - {canDelete && ( - <> - - handleDelete(rule)} /> - - )} - - } - > - - , - ]; -}; - -function copyToClipboard(text: string) { - navigator.clipboard?.writeText(text).then(() => { - appEvents.emit(AppEvents.alertSuccess, ['URL copied to clipboard']); - }); -} - -type PropsWithIdentifier = { identifier: RuleIdentifier }; - -const ExportMenuItem = ({ identifier }: PropsWithIdentifier) => { - const returnTo = location.pathname + location.search; - const url = createUrl(`/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/modify-export`, { - returnTo, - }); - - return ; -}; - -const EditButton = ({ identifier }: PropsWithIdentifier) => { - const returnTo = location.pathname + location.search; - const ruleIdentifier = ruleId.stringifyIdentifier(identifier); - const editURL = createUrl(`/alerting/${encodeURIComponent(ruleIdentifier)}/edit`, { returnTo }); - - return ( - - Edit - - ); -}; diff --git a/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx b/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx new file mode 100644 index 00000000000..b08c731d6f9 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.tsx @@ -0,0 +1,139 @@ +import React from 'react'; + +import { AppEvents } from '@grafana/data'; +import { ComponentSize, Dropdown, Menu } from '@grafana/ui'; +import appEvents from 'app/core/app_events'; +import MenuItemPauseRule from 'app/features/alerting/unified/components/MenuItemPauseRule'; +import MoreButton from 'app/features/alerting/unified/components/MoreButton'; +import { useRulePluginLinkExtension } from 'app/features/alerting/unified/plugins/useRulePluginLinkExtensions'; +import { isAlertingRule } from 'app/features/alerting/unified/utils/rules'; +import { CombinedRule, RuleIdentifier } from 'app/types/unified-alerting'; +import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; + +import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities'; +import { createShareLink, isLocalDevEnv, isOpenSourceEdition, makeRuleBasedSilenceLink } from '../../utils/misc'; +import * as ruleId from '../../utils/rule-id'; +import { createUrl } from '../../utils/url'; +import { DeclareIncidentMenuItem } from '../bridges/DeclareIncidentButton'; + +interface Props { + rule: CombinedRule; + identifier: RuleIdentifier; + showCopyLinkButton?: boolean; + handleDelete: (rule: CombinedRule) => void; + handleDuplicateRule: (identifier: RuleIdentifier) => void; + onPauseChange?: () => void; + buttonSize?: ComponentSize; + hideLabels?: boolean; +} + +/** + * Get a list of menu items + divider elements for rendering in an alert rule's + * dropdown menu + */ +const AlertRuleMenu = ({ + rule, + identifier, + showCopyLinkButton, + handleDelete, + handleDuplicateRule, + onPauseChange, + buttonSize, +}: Props) => { + // check all abilities and permissions + const [pauseSupported, pauseAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Pause); + const canPause = pauseSupported && pauseAllowed; + + const [deleteSupported, deleteAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Delete); + const canDelete = deleteSupported && deleteAllowed; + + const [duplicateSupported, duplicateAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Duplicate); + const canDuplicate = duplicateSupported && duplicateAllowed; + + const [silenceSupported, silenceAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Silence); + const canSilence = silenceSupported && silenceAllowed; + + const [exportSupported, exportAllowed] = useAlertRuleAbility(rule, AlertRuleAction.ModifyExport); + const canExport = exportSupported && exportAllowed; + + const ruleExtensionLinks = useRulePluginLinkExtension(rule); + + const extensionsAvailable = ruleExtensionLinks.length > 0; + + /** + * Since Incident isn't available as an open-source product we shouldn't show it for Open-Source licenced editions of Grafana. + * We should show it in development mode + */ + const shouldShowDeclareIncidentButton = + (!isOpenSourceEdition() || isLocalDevEnv()) && + isAlertingRule(rule.promRule) && + rule.promRule.state === PromAlertingRuleState.Firing; + const shareUrl = createShareLink(rule.namespace.rulesSource, rule); + + const showDivider = + [canPause, canSilence, shouldShowDeclareIncidentButton, canDuplicate].some(Boolean) && + [showCopyLinkButton, canExport].some(Boolean); + + const menuItems = ( + <> + {canPause && } + {canSilence && ( + + )} + {shouldShowDeclareIncidentButton && } + {canDuplicate && handleDuplicateRule(identifier)} />} + {showDivider && } + {shareUrl && copyToClipboard(shareUrl)} />} + {canExport && ( + ]} + /> + )} + {extensionsAvailable && ( + <> + + {ruleExtensionLinks.map((extension) => ( + + ))} + + )} + {canDelete && ( + <> + + handleDelete(rule)} /> + + )} + + ); + + return ( + {menuItems}}> + + + ); +}; + +function copyToClipboard(text: string) { + navigator.clipboard?.writeText(text).then(() => { + appEvents.emit(AppEvents.alertSuccess, ['URL copied to clipboard']); + }); +} + +type PropsWithIdentifier = { identifier: RuleIdentifier }; + +const ExportMenuItem = ({ identifier }: PropsWithIdentifier) => { + const returnTo = location.pathname + location.search; + const url = createUrl(`/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/modify-export`, { + returnTo, + }); + + return ; +}; + +export default AlertRuleMenu; diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx index 65d5453fee0..3f660cc17a2 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.test.tsx @@ -46,15 +46,15 @@ const ELEMENTS = { delete: byRole('menuitem', { name: /Delete/i }), }, pluginActions: { - sloDashboard: byRole('link', { name: /SLO dashboard/i }), + sloDashboard: byRole('menuitem', { name: /SLO dashboard/i }), declareIncident: byRole('link', { name: /Declare incident/i }), - assertsWorkbench: byRole('link', { name: /Open workbench/i }), + assertsWorkbench: byRole('menuitem', { name: /Open workbench/i }), }, }, }, }; -const { apiHandlers: pluginApiHandlers } = setupPlugins(plugins.slo, plugins.incident, plugins.asserts); +const { apiHandlers: pluginApiHandlers } = setupPlugins(plugins); const server = createMockGrafanaServer(...pluginApiHandlers); diff --git a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx index ed317f4b987..be5acf4370e 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/RuleViewer.tsx @@ -7,6 +7,7 @@ import { Alert, LinkButton, Stack, TabContent, Text, TextLink, useStyles2 } from import { PageInfoItem } from 'app/core/components/Page/types'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import InfoPausedRule from 'app/features/alerting/unified/components/InfoPausedRule'; +import { RuleActionsButtons } from 'app/features/alerting/unified/components/rules/RuleActionsButtons'; import { CombinedRule, RuleHealth, RuleIdentifier } from 'app/types/unified-alerting'; import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto'; @@ -31,8 +32,6 @@ import { WithReturnButton } from '../WithReturnButton'; import { decodeGrafanaNamespace } from '../expressions/util'; import { RedirectToCloneRule } from '../rules/CloneRule'; -import { useAlertRulePageActions } from './Actions'; -import { useDeleteModal } from './DeleteModal'; import { FederatedRuleWarning } from './FederatedRuleWarning'; import PausedBadge from './PausedBadge'; import { useAlertRule } from './RuleContext'; @@ -60,12 +59,6 @@ const RuleViewer = () => { // of duplicating provisioned alert rules const [duplicateRuleIdentifier, setDuplicateRuleIdentifier] = useState(); - const [deleteModal, showDeleteModal] = useDeleteModal(); - const actions = useAlertRulePageActions({ - handleDuplicateRule: setDuplicateRuleIdentifier, - handleDelete: showDeleteModal, - }); - const { annotations, promRule } = rule; const hasError = isErrorHealth(rule.promRule?.health); @@ -95,7 +88,7 @@ const RuleViewer = () => { ruleOrigin={ruleOrigin} /> )} - actions={actions} + actions={} info={createMetadata(rule)} subTitle={ @@ -128,7 +121,6 @@ const RuleViewer = () => { {activeTab === ActiveTab.Details &&
}
- {deleteModal} {duplicateRuleIdentifier && ( { + grantUserPermissions([ + AccessControlAction.AlertingRuleCreate, + AccessControlAction.AlertingRuleRead, + AccessControlAction.AlertingRuleUpdate, + AccessControlAction.AlertingRuleDelete, + AccessControlAction.AlertingInstanceCreate, + ]); + mockContextSrv.hasPermissionInMetadata.mockImplementation(() => true); + mockContextSrv.hasPermission.mockImplementation(() => true); +}; +const grantNoPermissions = () => { + grantUserPermissions([]); + mockContextSrv.hasPermissionInMetadata.mockImplementation(() => false); + mockContextSrv.hasPermission.mockImplementation(() => false); +}; + +const getMenuContents = async () => { + await screen.findByRole('menu'); + const allMenuItems = screen.queryAllByRole('menuitem').map((el) => el.textContent); + const allLinkItems = screen.queryAllByRole('link').map((el) => el.textContent); + + return [...allMenuItems, ...allLinkItems]; +}; + +setPluginExtensionsHook(() => ({ + extensions: [], + isLoading: false, +})); + +describe('RuleActionsButtons', () => { + it('renders correct options for grafana managed rule', async () => { + const user = userEvent.setup(); + grantAllPermissions(); + const mockRule = getGrafanaRule(); + + render(); + + await user.click(await ui.moreButton.find()); + + expect(await getMenuContents()).toMatchSnapshot(); + }); + + it('renders correct options for Cloud rule', async () => { + const user = userEvent.setup(); + grantAllPermissions(); + const mockRule = getCloudRule(); + const dataSource = mockDataSource({ id: 1 }); + + const defaultState = configureStore().getState(); + render(, { + preloadedState: produce(defaultState, (store) => { + store.unifiedAlerting.dataSources[dataSource.name] = { + loading: false, + dispatched: true, + result: { + id: 'test-ds', + name: dataSource.name, + rulerConfig: { + dataSourceName: dataSource.name, + apiVersion: 'config', + }, + }, + }; + }), + }); + + await user.click(await ui.moreButton.find()); + + expect(await getMenuContents()).toMatchSnapshot(); + }); + + it('renders minimal "More" menu when appropriate', async () => { + const user = userEvent.setup(); + grantNoPermissions(); + + const mockRule = getGrafanaRule({ promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Inactive }) }); + + render(); + + await user.click(await ui.moreButton.find()); + + expect(await getMenuContents()).toMatchSnapshot(); + }); + + it('does not allow deletion when rule is provisioned', async () => { + const user = userEvent.setup(); + grantAllPermissions(); + const mockRule = getGrafanaRule({ rulerRule: mockGrafanaRulerRule({ provenance: 'file' }) }); + + render(); + + await user.click(await ui.moreButton.find()); + + expect(screen.queryByText(/delete/i)).not.toBeInTheDocument(); + }); +}); diff --git a/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx index 484910ad4bb..b16add8fd93 100644 --- a/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleActionsButtons.tsx @@ -1,33 +1,20 @@ -import { css } from '@emotion/css'; -import { uniqueId } from 'lodash'; +import { css, cx } from '@emotion/css'; import React, { useState } from 'react'; import { useLocation } from 'react-router-dom'; import { GrafanaTheme2 } from '@grafana/data'; -import { - Button, - ClipboardButton, - ConfirmModal, - Dropdown, - Icon, - LinkButton, - Menu, - Tooltip, - useStyles2, - Stack, -} from '@grafana/ui'; -import { useAppNotification } from 'app/core/copy/appNotification'; -import MenuItemPauseRule from 'app/features/alerting/unified/components/MenuItemPauseRule'; +import { LinkButton, useStyles2, Stack } from '@grafana/ui'; +import AlertRuleMenu from 'app/features/alerting/unified/components/rule-viewer/AlertRuleMenu'; +import { useDeleteModal } from 'app/features/alerting/unified/components/rule-viewer/DeleteModal'; import { INSTANCES_DISPLAY_LIMIT } from 'app/features/alerting/unified/components/rules/RuleDetails'; import { useRulesFilter } from 'app/features/alerting/unified/hooks/useFilteredRules'; import { useDispatch } from 'app/types'; import { CombinedRule, RuleIdentifier, RulesSource } from 'app/types/unified-alerting'; import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities'; -import { useRulePluginLinkExtension } from '../../plugins/useRulePluginLinkExtensions'; -import { deleteRuleAction, fetchAllPromAndRulerRulesAction } from '../../state/actions'; -import { getRulesSourceName } from '../../utils/datasource'; -import { createShareLink, createViewLink } from '../../utils/misc'; +import { fetchPromAndRulerRulesAction } from '../../state/actions'; +import { GRAFANA_RULES_SOURCE_NAME, getRulesSourceName } from '../../utils/datasource'; +import { createViewLink } from '../../utils/misc'; import * as ruleId from '../../utils/rule-id'; import { isGrafanaRulerRule } from '../../utils/rules'; import { createUrl } from '../../utils/url'; @@ -39,230 +26,124 @@ export const matchesWidth = (width: number) => window.matchMedia(`(max-width: ${ interface Props { rule: CombinedRule; rulesSource: RulesSource; + /** + * Should we show the buttons in a "compact" state? + * i.e. without text and using smaller button sizes + */ + compact?: boolean; + showViewButton?: boolean; + showCopyLinkButton?: boolean; } -export const RuleActionsButtons = ({ rule, rulesSource }: Props) => { +/** + * **Action** buttons to show for an alert rule - e.g. "View", "Edit", "More..." + */ +export const RuleActionsButtons = ({ compact, showViewButton, showCopyLinkButton, rule, rulesSource }: Props) => { const dispatch = useDispatch(); const location = useLocation(); - const notifyApp = useAppNotification(); const style = useStyles2(getStyles); + const [deleteModal, showDeleteModal] = useDeleteModal(); const [redirectToClone, setRedirectToClone] = useState< { identifier: RuleIdentifier; isProvisioned: boolean } | undefined >(undefined); const { namespace, group, rulerRule } = rule; - const [ruleToDelete, setRuleToDelete] = useState(); const { hasActiveFilters } = useRulesFilter(); const returnTo = location.pathname + location.search; - const isViewMode = inViewMode(location.pathname); const isProvisioned = isGrafanaRulerRule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance); - const ruleExtensionLinks = useRulePluginLinkExtension(rule); - const [editRuleSupported, editRuleAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Update); - const [deleteRuleSupported, deleteRuleAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Delete); - const [duplicateRuleSupported, duplicateRuleAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Duplicate); - const [modifyExportSupported, modifyExportAllowed] = useAlertRuleAbility(rule, AlertRuleAction.ModifyExport); const canEditRule = editRuleSupported && editRuleAllowed; - const canDeleteRule = deleteRuleSupported && deleteRuleAllowed; - const canDuplicateRule = duplicateRuleSupported && duplicateRuleAllowed; - const canModifyExport = modifyExportSupported && modifyExportAllowed; const buttons: JSX.Element[] = []; - const moreActions: JSX.Element[] = []; - const deleteRule = () => { - if (ruleToDelete && ruleToDelete.rulerRule) { - const identifier = ruleId.fromRulerRule( - getRulesSourceName(ruleToDelete.namespace.rulesSource), - ruleToDelete.namespace.name, - ruleToDelete.group.name, - ruleToDelete.rulerRule - ); - - dispatch(deleteRuleAction(identifier, { navigateTo: isViewMode ? '/alerting/list' : undefined })); - setRuleToDelete(undefined); - } - }; - - const buildShareUrl = () => createShareLink(rulesSource, rule); + const buttonClasses = cx({ [style.compactButton]: compact }); + const buttonSize = compact ? 'sm' : 'md'; const sourceName = getRulesSourceName(rulesSource); - if (!isViewMode) { + const identifier = ruleId.fromCombinedRule(sourceName, rule); + + if (showViewButton) { buttons.push( - - - + + {!compact && 'View'} + ); } - if (rulerRule) { + if (rulerRule && canEditRule) { const identifier = ruleId.fromRulerRule(sourceName, namespace.name, group.name, rulerRule); - if (canEditRule) { - const editURL = createUrl(`/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/edit`, { - returnTo, - }); + const editURL = createUrl(`/alerting/${encodeURIComponent(ruleId.stringifyIdentifier(identifier))}/edit`, { + returnTo, + }); - buttons.push( - - - - ); + buttons.push( + + {!compact && 'Edit'} + + ); + } - moreActions.push( - { - // Uses INSTANCES_DISPLAY_LIMIT + 1 here as exporting LIMIT_ALERTS from RuleList has the side effect - // of breaking some unrelated tests in Policy.test.tsx due to mocking approach - const limitAlerts = hasActiveFilters ? undefined : INSTANCES_DISPLAY_LIMIT + 1; - // Trigger a re-fetch of the rules table - // TODO: Migrate rules table functionality to RTK Query, so we instead rely - // on tag invalidation (or optimistic cache updates) for this - dispatch(fetchAllPromAndRulerRulesAction(false, { limitAlerts })); - }} + return ( + + {buttons} + showDeleteModal(rule)} + handleDuplicateRule={() => setRedirectToClone({ identifier, isProvisioned })} + onPauseChange={() => { + // Uses INSTANCES_DISPLAY_LIMIT + 1 here as exporting LIMIT_ALERTS from RuleList has the side effect + // of breaking some unrelated tests in Policy.test.tsx due to mocking approach + const limitAlerts = hasActiveFilters ? undefined : INSTANCES_DISPLAY_LIMIT + 1; + // Trigger a re-fetch of the rules table + // TODO: Migrate rules table functionality to RTK Query, so we instead rely + // on tag invalidation (or optimistic cache updates) for this + dispatch(fetchPromAndRulerRulesAction({ rulesSourceName: GRAFANA_RULES_SOURCE_NAME, limitAlerts })); + }} + /> + {deleteModal} + {redirectToClone?.identifier && ( + setRedirectToClone(undefined)} /> - ); - } - - if (isViewMode) { - buttons.push( - { - notifyApp.error('Error while copying URL', copiedText); - }} - className={style.button} - size="sm" - getText={buildShareUrl} - > - Copy link to rule - - ); - } - - if (canDuplicateRule) { - moreActions.push( - setRedirectToClone({ identifier, isProvisioned })} /> - ); - } - - if (canModifyExport) { - moreActions.push( - - ); - } - } - - if (ruleExtensionLinks.length > 0) { - moreActions.push( - , - ...ruleExtensionLinks.map((extension) => ( - - )) - ); - } - - if (rulerRule && canDeleteRule) { - moreActions.push( - , - setRuleToDelete(rule)} /> - ); - } - - if (buttons.length || moreActions.length) { - return ( - <> - - {buttons.map((button, index) => ( - {button} - ))} - {moreActions.length > 0 && ( - - {moreActions.map((action) => ( - {action} - ))} - - } - > - - - )} - - {!!ruleToDelete && ( - -

- Deleting "{ruleToDelete.name}" will permanently remove it from your alert - rule list. -

-

Are you sure you want to delete this rule?

-
- } - confirmText="Yes, delete" - icon="exclamation-triangle" - onConfirm={deleteRule} - onDismiss={() => setRuleToDelete(undefined)} - /> - )} - - {redirectToClone && ( - setRedirectToClone(undefined)} - /> - )} - - ); - } - - return null; + )} + + ); }; -function inViewMode(pathname: string): boolean { - return pathname.endsWith('/view'); -} - -export const getStyles = (theme: GrafanaTheme2) => ({ - button: css` - padding: 0 ${theme.spacing(2)}; - `, +const getStyles = (theme: GrafanaTheme2) => ({ + compactButton: css({ + padding: `0 ${theme.spacing(2)}`, + }), }); diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx index 9f34c8c11c5..596e63b6bc3 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.test.tsx @@ -9,10 +9,8 @@ import { byRole } from 'testing-library-selector'; import { PluginExtensionTypes } from '@grafana/data'; import { usePluginLinkExtensions, setBackendSrv } from '@grafana/runtime'; import { backendSrv } from 'app/core/services/backend_srv'; -import { contextSrv } from 'app/core/services/context_srv'; import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; import { configureStore } from 'app/store/configureStore'; -import { AccessControlAction } from 'app/types'; import { CombinedRule } from 'app/types/unified-alerting'; import { AlertmanagersChoiceResponse } from '../../api/alertmanagerApi'; @@ -113,32 +111,6 @@ describe('RuleDetails RBAC', () => { expect(ui.actionButtons.delete.query()).not.toBeInTheDocument(); await waitFor(() => screen.queryByRole('button', { name: 'Declare incident' })); }); - - it('Should not render Silence button for users wihout the instance create permission', async () => { - // Arrange - jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false); - - // Act - renderRuleDetails(grafanaRule); - - // Assert - expect(ui.actionButtons.silence.query()).not.toBeInTheDocument(); - await waitFor(() => screen.queryByRole('button', { name: 'Declare incident' })); - }); - - it('Should render Silence button for users with the instance create permissions', async () => { - // Arrange - jest - .spyOn(contextSrv, 'hasPermission') - .mockImplementation((action) => action === AccessControlAction.AlertingInstanceCreate); - - // Act - renderRuleDetails(grafanaRule); - - // Assert - expect(await ui.actionButtons.silence.find()).toBeInTheDocument(); - await waitFor(() => screen.queryByRole('button', { name: 'Declare incident' })); - }); }); describe('Cloud rules action buttons', () => { diff --git a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx index 3e42e25f146..5023e5d0853 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetails.tsx @@ -12,8 +12,8 @@ import { isNullDate } from '../../utils/time'; import { AlertLabels } from '../AlertLabels'; import { DetailsField } from '../DetailsField'; -import { RuleDetailsActionButtons } from './RuleDetailsActionButtons'; import { RuleDetailsAnnotations } from './RuleDetailsAnnotations'; +import RuleDetailsButtons from './RuleDetailsButtons'; import { RuleDetailsDataSources } from './RuleDetailsDataSources'; import { RuleDetailsExpression } from './RuleDetailsExpression'; import { RuleDetailsMatchingInstances } from './RuleDetailsMatchingInstances'; @@ -37,7 +37,7 @@ export const RuleDetails = ({ rule }: Props) => { return (
- +
{} @@ -111,21 +111,21 @@ const EvaluationBehaviorSummary = ({ rule }: EvaluationBehaviorSummaryProps) => }; export const getStyles = (theme: GrafanaTheme2) => ({ - wrapper: css` - display: flex; - flex-direction: row; + wrapper: css({ + display: 'flex', + flexDirection: 'row', - ${theme.breakpoints.down('md')} { - flex-direction: column; - } - `, - leftSide: css` - flex: 1; - `, - rightSide: css` - ${theme.breakpoints.up('md')} { - padding-left: 90px; - width: 300px; - } - `, + [theme.breakpoints.down('md')]: { + flexDirection: 'column', + }, + }), + leftSide: css({ + flex: '1', + }), + rightSide: css({ + [theme.breakpoints.up('md')]: { + paddingLeft: '90px', + width: '300px', + }, + }), }); diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx deleted file mode 100644 index a9a6014380f..00000000000 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsActionButtons.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import { css } from '@emotion/css'; -import { uniqueId } from 'lodash'; -import React, { Fragment, useState } from 'react'; - -import { GrafanaTheme2, textUtil } from '@grafana/data'; -import { config, useReturnToPrevious } from '@grafana/runtime'; -import { Button, ConfirmModal, Dropdown, HorizontalGroup, Icon, LinkButton, Menu, useStyles2 } from '@grafana/ui'; -import { useDispatch } from 'app/types'; -import { CombinedRule, RuleIdentifier, RulesSource } from 'app/types/unified-alerting'; -import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; - -import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities'; -import { useStateHistoryModal } from '../../hooks/useStateHistoryModal'; -import { deleteRuleAction } from '../../state/actions'; -import { getAlertmanagerByUid } from '../../utils/alertmanager'; -import { Annotation } from '../../utils/constants'; -import { getRulesSourceName, isCloudRulesSource, isGrafanaRulesSource } from '../../utils/datasource'; -import { - createExploreLink, - createShareLink, - isLocalDevEnv, - isOpenSourceEdition, - makeRuleBasedSilenceLink, -} from '../../utils/misc'; -import * as ruleId from '../../utils/rule-id'; -import { isAlertingRule, isFederatedRuleGroup, isGrafanaRulerRule } from '../../utils/rules'; -import { DeclareIncidentButton } from '../bridges/DeclareIncidentButton'; - -import { RedirectToCloneRule } from './CloneRule'; - -interface Props { - rule: CombinedRule; - rulesSource: RulesSource; -} - -export const RuleDetailsActionButtons = ({ rule, rulesSource }: Props) => { - const style = useStyles2(getStyles); - const { group } = rule; - const { StateHistoryModal, showStateHistoryModal } = useStateHistoryModal(); - const dispatch = useDispatch(); - - const setReturnToPrevious = useReturnToPrevious(); - - const [ruleToDelete, setRuleToDelete] = useState(); - const [redirectToClone, setRedirectToClone] = useState< - { identifier: RuleIdentifier; isProvisioned: boolean } | undefined - >(undefined); - - const alertmanagerSourceName = isGrafanaRulesSource(rulesSource) - ? rulesSource - : getAlertmanagerByUid(rulesSource.jsonData.alertmanagerUid)?.name; - - const [silenceSupported, silenceAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Silence); - const [exploreSupported, exploreAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Explore); - - const buttons: JSX.Element[] = []; - const rightButtons: JSX.Element[] = []; - const moreActionsButtons: React.ReactElement[] = []; - - const deleteRule = () => { - if (ruleToDelete && ruleToDelete.rulerRule) { - const identifier = ruleId.fromRulerRule( - getRulesSourceName(ruleToDelete.namespace.rulesSource), - ruleToDelete.namespace.name, - ruleToDelete.group.name, - ruleToDelete.rulerRule - ); - - dispatch(deleteRuleAction(identifier, { navigateTo: undefined })); - setRuleToDelete(undefined); - } - }; - - const isFederated = isFederatedRuleGroup(group); - - const isFiringRule = isAlertingRule(rule.promRule) && rule.promRule.state === PromAlertingRuleState.Firing; - - const canSilence = silenceSupported && silenceAllowed && alertmanagerSourceName; - - const buildShareUrl = () => createShareLink(rulesSource, rule); - - // explore does not support grafana rule queries atm - // neither do "federated rules" - if (isCloudRulesSource(rulesSource) && exploreSupported && exploreAllowed && !isFederated) { - buttons.push( - - See graph - - ); - } - if (rule.annotations[Annotation.runbookURL]) { - buttons.push( - - View runbook - - ); - } - if (rule.annotations[Annotation.dashboardUID]) { - const dashboardUID = rule.annotations[Annotation.dashboardUID]; - const isReturnToPreviousEnabled = config.featureToggles.returnToPrevious; - if (dashboardUID) { - buttons.push( - { - setReturnToPrevious(rule.name); - }} - > - Go to dashboard - - ); - const panelId = rule.annotations[Annotation.panelID]; - if (panelId) { - buttons.push( - { - setReturnToPrevious(rule.name); - }} - > - Go to panel - - ); - } - } - } - - if (canSilence) { - buttons.push( - - Silence - - ); - } - - if (isGrafanaRulerRule(rule.rulerRule)) { - buttons.push( - - - {StateHistoryModal} - - ); - } - - if (isFiringRule && shouldShowDeclareIncidentButton()) { - buttons.push( - - - - ); - } - - if (buttons.length || rightButtons.length || moreActionsButtons.length) { - return ( - <> -
- {buttons.length ? buttons :
} - - {rightButtons.length && rightButtons} - {moreActionsButtons.length && ( - - {moreActionsButtons.map((action) => ( - {action} - ))} - - } - > - - - )} - -
- {!!ruleToDelete && ( - setRuleToDelete(undefined)} - /> - )} - {redirectToClone && ( - setRedirectToClone(undefined)} - /> - )} - - ); - } - - return null; -}; - -/** - * Since Incident isn't available as an open-source product we shouldn't show it for Open-Source licenced editions of Grafana. - * We should show it in development mode - */ -function shouldShowDeclareIncidentButton() { - return !isOpenSourceEdition() || isLocalDevEnv(); -} - -export const getStyles = (theme: GrafanaTheme2) => ({ - wrapper: css` - padding: 0 0 ${theme.spacing(2)} 0; - gap: ${theme.spacing(1)}; - display: flex; - flex-direction: row; - justify-content: space-between; - flex-wrap: wrap; - border-bottom: solid 1px ${theme.colors.border.medium}; - `, -}); diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsButtons.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsButtons.tsx new file mode 100644 index 00000000000..2008d401a77 --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsButtons.tsx @@ -0,0 +1,127 @@ +import React, { Fragment } from 'react'; + +import { textUtil } from '@grafana/data'; +import { config, useReturnToPrevious } from '@grafana/runtime'; +import { Button, LinkButton, Stack } from '@grafana/ui'; +import { CombinedRule, RulesSource } from 'app/types/unified-alerting'; + +import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities'; +import { useStateHistoryModal } from '../../hooks/useStateHistoryModal'; +import { Annotation } from '../../utils/constants'; +import { isCloudRulesSource } from '../../utils/datasource'; +import { createExploreLink } from '../../utils/misc'; +import { isFederatedRuleGroup, isGrafanaRulerRule } from '../../utils/rules'; + +interface Props { + rule: CombinedRule; + rulesSource: RulesSource; +} + +/** + * Buttons to display on an expanded alert rule in the list view + * + * e.g. "Show state history", "Go to dashboard" + * + * Shouldn't include *actions* for the alert rule, just navigation items + */ +const RuleDetailsButtons = ({ rule, rulesSource }: Props) => { + const { group } = rule; + const { StateHistoryModal, showStateHistoryModal } = useStateHistoryModal(); + + const setReturnToPrevious = useReturnToPrevious(); + + const [exploreSupported, exploreAllowed] = useAlertRuleAbility(rule, AlertRuleAction.Explore); + + const buttons: JSX.Element[] = []; + + const isFederated = isFederatedRuleGroup(group); + + // explore does not support grafana rule queries atm + // neither do "federated rules" + if (isCloudRulesSource(rulesSource) && exploreSupported && exploreAllowed && !isFederated) { + buttons.push( + + See graph + + ); + } + if (rule.annotations[Annotation.runbookURL]) { + buttons.push( + + View runbook + + ); + } + if (rule.annotations[Annotation.dashboardUID]) { + const dashboardUID = rule.annotations[Annotation.dashboardUID]; + const isReturnToPreviousEnabled = config.featureToggles.returnToPrevious; + if (dashboardUID) { + buttons.push( + { + setReturnToPrevious(rule.name); + }} + > + Go to dashboard + + ); + const panelId = rule.annotations[Annotation.panelID]; + if (panelId) { + buttons.push( + { + setReturnToPrevious(rule.name); + }} + > + Go to panel + + ); + } + } + } + + if (isGrafanaRulerRule(rule.rulerRule)) { + buttons.push( + + + {StateHistoryModal} + + ); + } + + return buttons.length ? {buttons} : null; +}; + +export default RuleDetailsButtons; diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx index 7f29dcc73ac..d6489633255 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx @@ -6,11 +6,12 @@ import { MemoryRouter } from 'react-router-dom'; import { byRole } from 'testing-library-selector'; import { setPluginExtensionsHook } from '@grafana/runtime'; +import { mockApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; import { configureStore } from 'app/store/configureStore'; import { CombinedRule } from 'app/types/unified-alerting'; import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities'; -import { getCloudRule, getGrafanaRule } from '../../mocks'; +import { getCloudRule, getGrafanaRule, getMockPluginMeta } from '../../mocks'; import { RulesTable } from './RulesTable'; @@ -29,7 +30,7 @@ const ui = { actionButtons: { edit: byRole('link', { name: 'Edit' }), view: byRole('link', { name: 'View' }), - more: byRole('button', { name: 'More' }), + more: byRole('button', { name: /more-actions/i }), }, moreActionItems: { delete: byRole('menuitem', { name: 'Delete' }), @@ -49,8 +50,14 @@ function renderRulesTable(rule: CombinedRule) { } const user = userEvent.setup(); +const server = setupMswServer(); describe('RulesTable RBAC', () => { + beforeEach(() => { + mockApi(server).plugins.getPluginSettings({ + ...getMockPluginMeta('grafana-incident-app', 'Grafana Incident'), + }); + }); describe('Grafana rules action buttons', () => { const grafanaRule = getGrafanaRule({ name: 'Grafana' }); diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.tsx index e1ae729a4f8..0a40744e898 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.tsx @@ -266,7 +266,7 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean, showNe label: 'Actions', // eslint-disable-next-line react/display-name renderCell: ({ data: rule }) => { - return ; + return ; }, size: '200px', }); diff --git a/public/app/features/alerting/unified/components/rules/__snapshots__/RuleActionsButtons.test.tsx.snap b/public/app/features/alerting/unified/components/rules/__snapshots__/RuleActionsButtons.test.tsx.snap new file mode 100644 index 00000000000..7e8ec05632e --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/__snapshots__/RuleActionsButtons.test.tsx.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RuleActionsButtons renders correct options for Cloud rule 1`] = ` +[ + "Duplicate", + "Copy link", + "Delete", + "Declare incident", +] +`; + +exports[`RuleActionsButtons renders correct options for grafana managed rule 1`] = ` +[ + "Pause evaluation", + "Duplicate", + "Copy link", + "Export", + "Delete", + "Silence notifications", + "Declare incident", +] +`; + +exports[`RuleActionsButtons renders minimal "More" menu when appropriate 1`] = ` +[ + "Copy link", +] +`; diff --git a/public/app/features/alerting/unified/hooks/__snapshots__/useAbilities.test.tsx.snap b/public/app/features/alerting/unified/hooks/__snapshots__/useAbilities.test.tsx.snap index 2b0417df565..02137bf67eb 100644 --- a/public/app/features/alerting/unified/hooks/__snapshots__/useAbilities.test.tsx.snap +++ b/public/app/features/alerting/unified/hooks/__snapshots__/useAbilities.test.tsx.snap @@ -18,6 +18,10 @@ exports[`AlertRule abilities should report no permissions while we are loading d false, false, ], + "pause-alert-rule": [ + false, + false, + ], "silence-alert-rule": [ false, false, @@ -51,6 +55,10 @@ exports[`AlertRule abilities should report that all actions are supported for a true, false, ], + "pause-alert-rule": [ + true, + false, + ], "silence-alert-rule": [ true, false, diff --git a/public/app/features/alerting/unified/hooks/useAbilities.ts b/public/app/features/alerting/unified/hooks/useAbilities.ts index 14ffa869fc5..9ba6999239c 100644 --- a/public/app/features/alerting/unified/hooks/useAbilities.ts +++ b/public/app/features/alerting/unified/hooks/useAbilities.ts @@ -69,6 +69,7 @@ export enum AlertRuleAction { Explore = 'explore-alert-rule', Silence = 'silence-alert-rule', ModifyExport = 'modify-export-rule', + Pause = 'pause-alert-rule', } // this enum lists all of the actions we can perform within alerting in general, not linked to a specific @@ -178,6 +179,7 @@ export function useAllAlertRuleAbilities(rule: CombinedRule): Abilities PluginMeta = (id, name) => { + return { + name, + id, + type: PluginType.app, + module: `plugins/${id}/module`, + baseUrl: `public/plugins/${id}`, + info: { + author: { name: 'Grafana Labs' }, + description: name, + updated: '', + version: '', + links: [], + logos: { + small: '', + large: '', + }, + screenshots: [], }, - screenshots: [], - }, + }; }; -export const labelsPluginMetaMock: PluginMeta = { - name: 'Grafana IRM Labels', - id: 'grafana-labels-app', - type: PluginType.app, - module: 'plugins/grafana-labels-app/module', - baseUrl: 'public/plugins/grafana-labels-app', - info: { - author: { name: 'Grafana Labs' }, - description: '', - updated: '', - version: '', - links: [], - logos: { - small: '', - large: '', - }, - screenshots: [], - }, -}; +export const labelsPluginMetaMock = getMockPluginMeta('grafana-labels-app', 'Grafana IRM Labels'); +export const onCallPluginMetaMock = getMockPluginMeta('grafana-oncall-app', 'Grafana OnCall'); diff --git a/public/app/features/alerting/unified/mocks/folders.ts b/public/app/features/alerting/unified/mocks/folders.ts new file mode 100644 index 00000000000..9b20e2d826f --- /dev/null +++ b/public/app/features/alerting/unified/mocks/folders.ts @@ -0,0 +1,6 @@ +import { HttpResponse, http } from 'msw'; + +import { mockFolder } from 'app/features/alerting/unified/mocks'; + +export const folderHandler = (response = mockFolder()) => + http.get(`/api/folders/:folderUid`, () => HttpResponse.json(response)); diff --git a/public/app/features/alerting/unified/mocks/plugins.ts b/public/app/features/alerting/unified/mocks/plugins.ts index 1a97fc39f50..54d0038f757 100644 --- a/public/app/features/alerting/unified/mocks/plugins.ts +++ b/public/app/features/alerting/unified/mocks/plugins.ts @@ -1,10 +1,12 @@ import { http, HttpResponse } from 'msw'; import { PluginMeta } from '@grafana/data'; +import { plugins } from 'app/features/alerting/unified/testSetup/plugins'; -export const pluginsHandler = (pluginsRegistry: Map) => - http.get<{ pluginId: string }>(`/api/plugins/:pluginId/settings`, ({ params: { pluginId } }) => - pluginsRegistry.has(pluginId) - ? HttpResponse.json(pluginsRegistry.get(pluginId)!) - : HttpResponse.json({ message: 'Plugin not found, no installed plugin with that id' }, { status: 404 }) - ); +export const pluginsHandler = (pluginsArray: PluginMeta[] = plugins) => + http.get<{ pluginId: string }>(`/api/plugins/:pluginId/settings`, ({ params: { pluginId } }) => { + const matchingPlugin = pluginsArray.find((plugin) => plugin.id === pluginId); + return matchingPlugin + ? HttpResponse.json(matchingPlugin) + : HttpResponse.json({ message: 'Plugin not found, no installed plugin with that id' }, { status: 404 }); + }); diff --git a/public/app/features/alerting/unified/mocks/server/handlers.ts b/public/app/features/alerting/unified/mocks/server/handlers.ts index 7fa5949fdc7..99871367855 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers.ts @@ -7,6 +7,8 @@ import { alertmanagerChoiceHandler, } from 'app/features/alerting/unified/mocks/alertmanagerApi'; import { datasourceBuildInfoHandler } from 'app/features/alerting/unified/mocks/datasources'; +import { folderHandler } from 'app/features/alerting/unified/mocks/folders'; +import { pluginsHandler } from 'app/features/alerting/unified/mocks/plugins'; import { silenceCreateHandler, silenceGetHandler, @@ -20,6 +22,10 @@ const allHandlers = [ alertmanagerChoiceHandler(), alertmanagerAlertsListHandler(), + folderHandler(), + + pluginsHandler(), + silencesListHandler(), silenceGetHandler(), silenceCreateHandler(), diff --git a/public/app/features/alerting/unified/testSetup/plugins.ts b/public/app/features/alerting/unified/testSetup/plugins.ts index 1ef1b4fae87..9cd8041afdf 100644 --- a/public/app/features/alerting/unified/testSetup/plugins.ts +++ b/public/app/features/alerting/unified/testSetup/plugins.ts @@ -5,11 +5,8 @@ import { config } from '@grafana/runtime'; import { pluginsHandler } from '../mocks/plugins'; -export function setupPlugins(...plugins: PluginMeta[]): { apiHandlers: RequestHandler[] } { - const pluginsRegistry = new Map(); - plugins.forEach((plugin) => pluginsRegistry.set(plugin.id, plugin)); - - pluginsRegistry.forEach((plugin) => { +export function setupPlugins(plugins: PluginMeta[]): { apiHandlers: RequestHandler[] } { + plugins.forEach((plugin) => { config.apps[plugin.id] = { id: plugin.id, path: plugin.baseUrl, @@ -20,12 +17,12 @@ export function setupPlugins(...plugins: PluginMeta[]): { apiHandlers: RequestHa }); return { - apiHandlers: [pluginsHandler(pluginsRegistry)], + apiHandlers: [pluginsHandler(plugins)], }; } -export const plugins: Record = { - slo: { +export const plugins: PluginMeta[] = [ + { id: 'grafana-slo-app', name: 'SLO dashboard', type: PluginType.app, @@ -48,7 +45,7 @@ export const plugins: Record = { module: 'public/plugins/grafana-slo-app/module.js', baseUrl: 'public/plugins/grafana-slo-app', }, - incident: { + { id: 'grafana-incident-app', name: 'Incident management', type: PluginType.app, @@ -71,7 +68,7 @@ export const plugins: Record = { module: 'public/plugins/grafana-incident-app/module.js', baseUrl: 'public/plugins/grafana-incident-app', }, - asserts: { + { id: 'grafana-asserts-app', name: 'Asserts', type: PluginType.app, @@ -94,4 +91,4 @@ export const plugins: Record = { module: 'public/plugins/grafana-asserts-app/module.js', baseUrl: 'public/plugins/grafana-asserts-app', }, -}; +]; diff --git a/public/app/features/alerting/unified/utils/misc.ts b/public/app/features/alerting/unified/utils/misc.ts index 8032c2f4487..9106eb4a022 100644 --- a/public/app/features/alerting/unified/utils/misc.ts +++ b/public/app/features/alerting/unified/utils/misc.ts @@ -5,7 +5,7 @@ import { GrafanaEdition } from '@grafana/data/src/types/config'; import { config, isFetchError } from '@grafana/runtime'; import { DataSourceRef } from '@grafana/schema'; import { escapePathSeparators } from 'app/features/alerting/unified/utils/rule-id'; -import { alertInstanceKey } from 'app/features/alerting/unified/utils/rules'; +import { alertInstanceKey, isGrafanaRulerRule } from 'app/features/alerting/unified/utils/rules'; import { SortOrder } from 'app/plugins/panel/alertlist/types'; import { Alert, CombinedRule, FilterState, RulesSource, SilenceFilterState } from 'app/types/unified-alerting'; import { @@ -54,14 +54,16 @@ export function createMuteTimingLink(muteTimingName: string, alertManagerSourceN }); } -export function createShareLink(ruleSource: RulesSource, rule: CombinedRule): string { +export function createShareLink(ruleSource: RulesSource, rule: CombinedRule): string | undefined { if (isCloudRulesSource(ruleSource)) { return createAbsoluteUrl( `/alerting/${encodeURIComponent(ruleSource.name)}/${encodeURIComponent(escapePathSeparators(rule.name))}/find` ); + } else if (isGrafanaRulerRule(rule.rulerRule)) { + return createUrl(`/alerting/grafana/${rule.rulerRule.grafana_alert.uid}/view`); } - return window.location.href.split('?')[0]; + return; } export function arrayToRecord(items: Array<{ key: string; value: string }>): Record { From a54df47976f4588b8af3fae6251ca53da45f96ce Mon Sep 17 00:00:00 2001 From: Kevin Yu Date: Tue, 30 Apr 2024 17:06:16 +0100 Subject: [PATCH 222/222] CloudWatch: Add labels for Metric Query type queries (#85766) * CloudWatch: Fix metric query with group by not being labelled in alerts * just use one key for the labels * not needed * unused function * add tests * pr comments * fetch dimensions to build labels for MetricQuery type queries * pr comments * group cache related tests and use fresh cache for non-cache related tests * don't cache empty values --- .../get_dimension_values_for_wildcards.go | 35 ++- ...get_dimension_values_for_wildcards_test.go | 224 +++++++++++------- .../cloudwatch/models/cloudwatch_query.go | 20 +- pkg/tsdb/cloudwatch/response_parser.go | 19 +- pkg/tsdb/cloudwatch/response_parser_test.go | 90 ++++++- pkg/tsdb/cloudwatch/time_series_query.go | 17 +- .../SQLBuilderEditor/utils.ts | 17 +- 7 files changed, 317 insertions(+), 105 deletions(-) diff --git a/pkg/tsdb/cloudwatch/get_dimension_values_for_wildcards.go b/pkg/tsdb/cloudwatch/get_dimension_values_for_wildcards.go index 3fc8f469942..96260038064 100644 --- a/pkg/tsdb/cloudwatch/get_dimension_values_for_wildcards.go +++ b/pkg/tsdb/cloudwatch/get_dimension_values_for_wildcards.go @@ -12,17 +12,27 @@ import ( ) // getDimensionValues gets the actual dimension values for dimensions with a wildcard -func (e *cloudWatchExecutor) getDimensionValuesForWildcards(ctx context.Context, region string, - client models.CloudWatchMetricsAPIProvider, origQueries []*models.CloudWatchQuery, tagValueCache *cache.Cache, listMetricsPageLimit int) ([]*models.CloudWatchQuery, error) { +func (e *cloudWatchExecutor) getDimensionValuesForWildcards( + ctx context.Context, + region string, + client models.CloudWatchMetricsAPIProvider, + origQueries []*models.CloudWatchQuery, + tagValueCache *cache.Cache, + listMetricsPageLimit int, + shouldSkip func(*models.CloudWatchQuery) bool) ([]*models.CloudWatchQuery, error) { metricsClient := clients.NewMetricsClient(client, listMetricsPageLimit) service := services.NewListMetricsService(metricsClient) // create copies of the original query. All the fields besides Dimensions are primitives queries := copyQueries(origQueries) + queries = addWildcardDimensionsForMetricQueryTypeQueries(queries) for _, query := range queries { + if shouldSkip(query) { + continue + } for dimensionKey, values := range query.Dimensions { // if the dimension is not a wildcard, skip it - if len(values) != 1 || query.MatchExact || (len(values) == 1 && values[0] != "*") { + if len(values) != 1 || (len(values) == 1 && values[0] != "*") { continue } @@ -85,3 +95,22 @@ func copyQueries(origQueries []*models.CloudWatchQuery) []*models.CloudWatchQuer } return newQueries } + +// addWildcardDimensionsForMetricQueryTypeQueries adds wildcard dimensions if there is +// a `GROUP BY` clause in the query. This is used for MetricQuery type queries so we can +// build labels when we build the data frame. +func addWildcardDimensionsForMetricQueryTypeQueries(queries []*models.CloudWatchQuery) []*models.CloudWatchQuery { + for i, q := range queries { + if q.MetricQueryType != models.MetricQueryTypeQuery || q.MetricEditorMode == models.MetricEditorModeRaw || q.Sql.GroupBy == nil || len(q.Sql.GroupBy.Expressions) == 0 { + continue + } + + for _, expr := range q.Sql.GroupBy.Expressions { + if expr.Property.Name != nil && *expr.Property.Name != "" { + queries[i].Dimensions[*expr.Property.Name] = []string{"*"} + } + } + } + + return queries +} diff --git a/pkg/tsdb/cloudwatch/get_dimension_values_for_wildcards_test.go b/pkg/tsdb/cloudwatch/get_dimension_values_for_wildcards_test.go index 7a18ba59f8c..2a3f5c36e78 100644 --- a/pkg/tsdb/cloudwatch/get_dimension_values_for_wildcards_test.go +++ b/pkg/tsdb/cloudwatch/get_dimension_values_for_wildcards_test.go @@ -6,6 +6,7 @@ import ( "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils" @@ -13,96 +14,159 @@ import ( "github.com/stretchr/testify/assert" ) +func noSkip(q *models.CloudWatchQuery) bool { return false } +func skip(q *models.CloudWatchQuery) bool { return true } + func TestGetDimensionValuesForWildcards(t *testing.T) { executor := &cloudWatchExecutor{im: defaultTestInstanceManager(), logger: log.NewNullLogger()} ctx := context.Background() - tagValueCache := cache.New(0, 0) - t.Run("Should not change non-wildcard dimension value", func(t *testing.T) { - query := getBaseQuery() - query.MetricName = "Test_MetricName1" - query.Dimensions = map[string][]string{"Test_DimensionName1": {"Value1"}} - queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", nil, []*models.CloudWatchQuery{query}, tagValueCache, 50) - assert.Nil(t, err) - assert.Len(t, queries, 1) - assert.NotNil(t, queries[0].Dimensions["Test_DimensionName1"], 1) - assert.Equal(t, []string{"Value1"}, queries[0].Dimensions["Test_DimensionName1"]) + t.Run("Tag value cache", func(t *testing.T) { + tagValueCache := cache.New(0, 0) + + t.Run("Should use cache for previously fetched value", func(t *testing.T) { + query := getBaseQuery() + query.MetricName = "Test_MetricName" + query.Dimensions = map[string][]string{"Test_DimensionName": {"*"}} + query.MetricQueryType = models.MetricQueryTypeSearch + query.MatchExact = false + api := &mocks.MetricsAPI{Metrics: []*cloudwatch.Metric{ + {MetricName: utils.Pointer("Test_MetricName"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName"), Value: utils.Pointer("Value")}}}, + }} + api.On("ListMetricsPagesWithContext").Return(nil) + _, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", api, []*models.CloudWatchQuery{query}, tagValueCache, 50, noSkip) + assert.Nil(t, err) + // make sure the original query wasn't altered + assert.Equal(t, map[string][]string{"Test_DimensionName": {"*"}}, query.Dimensions) + + //setting the api to nil confirms that it's using the cached value + queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", nil, []*models.CloudWatchQuery{query}, tagValueCache, 50, noSkip) + assert.Nil(t, err) + assert.Len(t, queries, 1) + assert.Equal(t, map[string][]string{"Test_DimensionName": {"Value"}}, queries[0].Dimensions) + api.AssertExpectations(t) + }) + + t.Run("Should not cache when no values are returned", func(t *testing.T) { + query := getBaseQuery() + query.MetricName = "Test_MetricName" + query.Dimensions = map[string][]string{"Test_DimensionName2": {"*"}} + query.MetricQueryType = models.MetricQueryTypeSearch + query.MatchExact = false + api := &mocks.MetricsAPI{Metrics: []*cloudwatch.Metric{}} + api.On("ListMetricsPagesWithContext").Return(nil) + queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", api, []*models.CloudWatchQuery{query}, tagValueCache, 50, noSkip) + assert.Nil(t, err) + assert.Len(t, queries, 1) + // assert that the values was set to an empty array + assert.Equal(t, map[string][]string{"Test_DimensionName2": {}}, queries[0].Dimensions) + + // Confirm that it calls the api again if the last call did not return any values + api.Metrics = []*cloudwatch.Metric{ + {MetricName: utils.Pointer("Test_MetricName"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName2"), Value: utils.Pointer("Value")}}}, + } + api.On("ListMetricsPagesWithContext").Return(nil) + queries, err = executor.getDimensionValuesForWildcards(ctx, "us-east-1", api, []*models.CloudWatchQuery{query}, tagValueCache, 50, noSkip) + assert.Nil(t, err) + assert.Len(t, queries, 1) + assert.Equal(t, map[string][]string{"Test_DimensionName2": {"Value"}}, queries[0].Dimensions) + api.AssertExpectations(t) + }) }) - t.Run("Should not change exact dimension value", func(t *testing.T) { - query := getBaseQuery() - query.MetricName = "Test_MetricName1" - query.Dimensions = map[string][]string{"Test_DimensionName1": {"*"}} - queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", nil, []*models.CloudWatchQuery{query}, tagValueCache, 50) - assert.Nil(t, err) - assert.Len(t, queries, 1) - assert.NotNil(t, queries[0].Dimensions["Test_DimensionName1"]) - assert.Equal(t, []string{"*"}, queries[0].Dimensions["Test_DimensionName1"]) + t.Run("MetricSearch query type", func(t *testing.T) { + t.Run("Should not change non-wildcard dimension value", func(t *testing.T) { + query := getBaseQuery() + query.MetricName = "Test_MetricName1" + query.Dimensions = map[string][]string{"Test_DimensionName1": {"Value1"}} + query.MetricQueryType = models.MetricQueryTypeSearch + queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", nil, []*models.CloudWatchQuery{query}, cache.New(0, 0), 50, skip) + assert.Nil(t, err) + assert.Len(t, queries, 1) + assert.NotNil(t, queries[0].Dimensions["Test_DimensionName1"], 1) + assert.Equal(t, []string{"Value1"}, queries[0].Dimensions["Test_DimensionName1"]) + }) + + t.Run("Should not change exact dimension value", func(t *testing.T) { + query := getBaseQuery() + query.MetricName = "Test_MetricName1" + query.Dimensions = map[string][]string{"Test_DimensionName1": {"*"}} + query.MetricQueryType = models.MetricQueryTypeSearch + queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", nil, []*models.CloudWatchQuery{query}, cache.New(0, 0), 50, skip) + assert.Nil(t, err) + assert.Len(t, queries, 1) + assert.NotNil(t, queries[0].Dimensions["Test_DimensionName1"]) + assert.Equal(t, []string{"*"}, queries[0].Dimensions["Test_DimensionName1"]) + }) + + t.Run("Should change wildcard dimension value", func(t *testing.T) { + query := getBaseQuery() + query.MetricName = "Test_MetricName1" + query.Dimensions = map[string][]string{"Test_DimensionName1": {"*"}} + query.MetricQueryType = models.MetricQueryTypeSearch + query.MatchExact = false + api := &mocks.MetricsAPI{Metrics: []*cloudwatch.Metric{ + {MetricName: utils.Pointer("Test_MetricName1"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Value1")}, {Name: utils.Pointer("Test_DimensionName2"), Value: utils.Pointer("Value2")}}}, + {MetricName: utils.Pointer("Test_MetricName2"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Value3")}}}, + {MetricName: utils.Pointer("Test_MetricName3"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Value4")}}}, + {MetricName: utils.Pointer("Test_MetricName4"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Value2")}}}, + }} + api.On("ListMetricsPagesWithContext").Return(nil) + queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", api, []*models.CloudWatchQuery{query}, cache.New(0, 0), 50, noSkip) + assert.Nil(t, err) + assert.Len(t, queries, 1) + assert.Equal(t, map[string][]string{"Test_DimensionName1": {"Value1", "Value2", "Value3", "Value4"}}, queries[0].Dimensions) + api.AssertExpectations(t) + }) }) - t.Run("Should change wildcard dimension value", func(t *testing.T) { - query := getBaseQuery() - query.MetricName = "Test_MetricName1" - query.Dimensions = map[string][]string{"Test_DimensionName1": {"*"}} - query.MatchExact = false - api := &mocks.MetricsAPI{Metrics: []*cloudwatch.Metric{ - {MetricName: utils.Pointer("Test_MetricName1"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Value1")}, {Name: utils.Pointer("Test_DimensionName2"), Value: utils.Pointer("Value2")}}}, - {MetricName: utils.Pointer("Test_MetricName2"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Value3")}}}, - {MetricName: utils.Pointer("Test_MetricName3"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Value4")}}}, - {MetricName: utils.Pointer("Test_MetricName4"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Value2")}}}, - }} - api.On("ListMetricsPagesWithContext").Return(nil) - queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", api, []*models.CloudWatchQuery{query}, tagValueCache, 50) - assert.Nil(t, err) - assert.Len(t, queries, 1) - assert.Equal(t, map[string][]string{"Test_DimensionName1": {"Value1", "Value2", "Value3", "Value4"}}, queries[0].Dimensions) - api.AssertExpectations(t) - }) + t.Run("MetricQuery query type", func(t *testing.T) { + t.Run("Should fetch dimensions when there is a `GROUP BY` clause", func(t *testing.T) { + query := getBaseQuery() + query.MetricName = "Test_MetricName" + query.Dimensions = map[string][]string{} + query.Sql.GroupBy = &models.SQLExpressionGroupBy{ + Expressions: []dataquery.QueryEditorGroupByExpression{ + { + Property: dataquery.QueryEditorProperty{Name: utils.Pointer("Test_DimensionName1"), Type: "string"}, + Type: "groupBy", + }, + { + Property: dataquery.QueryEditorProperty{Name: utils.Pointer("Test_DimensionName2"), Type: "string"}, + Type: "groupBy", + }, + }, + Type: "and", + } + query.MetricQueryType = models.MetricQueryTypeQuery - t.Run("Should use cache for previously fetched value", func(t *testing.T) { - query := getBaseQuery() - query.MetricName = "Test_MetricName" - query.Dimensions = map[string][]string{"Test_DimensionName": {"*"}} - query.MatchExact = false - api := &mocks.MetricsAPI{Metrics: []*cloudwatch.Metric{ - {MetricName: utils.Pointer("Test_MetricName"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName"), Value: utils.Pointer("Value")}}}, - }} - api.On("ListMetricsPagesWithContext").Return(nil) - _, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", api, []*models.CloudWatchQuery{query}, tagValueCache, 50) - assert.Nil(t, err) - // make sure the original query wasn't altered - assert.Equal(t, map[string][]string{"Test_DimensionName": {"*"}}, query.Dimensions) + api := &mocks.MetricsAPI{Metrics: []*cloudwatch.Metric{ + {MetricName: utils.Pointer("Test_MetricName"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Dimension1Value1")}, {Name: utils.Pointer("Test_DimensionName2"), Value: utils.Pointer("Dimension2Value1")}}}, + {MetricName: utils.Pointer("Test_MetricName"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Dimension1Value2")}, {Name: utils.Pointer("Test_DimensionName2"), Value: utils.Pointer("Dimension2Value2")}}}, + {MetricName: utils.Pointer("Test_MetricName"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Dimension1Value3")}, {Name: utils.Pointer("Test_DimensionName2"), Value: utils.Pointer("Dimension2Value3")}}}, + {MetricName: utils.Pointer("Test_MetricName"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName1"), Value: utils.Pointer("Dimension1Value4")}, {Name: utils.Pointer("Test_DimensionName2"), Value: utils.Pointer("Dimension2Value4")}}}, + }} + api.On("ListMetricsPagesWithContext").Return(nil) + queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", api, []*models.CloudWatchQuery{query}, cache.New(0, 0), 50, noSkip) + assert.Nil(t, err) + assert.Len(t, queries, 1) + assert.Equal(t, map[string][]string{ + "Test_DimensionName1": {"Dimension1Value1", "Dimension1Value2", "Dimension1Value3", "Dimension1Value4"}, + "Test_DimensionName2": {"Dimension2Value1", "Dimension2Value2", "Dimension2Value3", "Dimension2Value4"}, + }, queries[0].Dimensions) + api.AssertExpectations(t) + }) - //setting the api to nil confirms that it's using the cached value - queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", nil, []*models.CloudWatchQuery{query}, tagValueCache, 50) - assert.Nil(t, err) - assert.Len(t, queries, 1) - assert.Equal(t, map[string][]string{"Test_DimensionName": {"Value"}}, queries[0].Dimensions) - api.AssertExpectations(t) - }) + t.Run("Should not fetch dimensions when there is not a `GROUP BY` clause", func(t *testing.T) { + query := getBaseQuery() + query.MetricName = "Test_MetricName" + query.Dimensions = map[string][]string{} + query.MetricQueryType = models.MetricQueryTypeQuery - t.Run("Should not cache when no values are returned", func(t *testing.T) { - query := getBaseQuery() - query.MetricName = "Test_MetricName" - query.Dimensions = map[string][]string{"Test_DimensionName2": {"*"}} - query.MatchExact = false - api := &mocks.MetricsAPI{Metrics: []*cloudwatch.Metric{}} - api.On("ListMetricsPagesWithContext").Return(nil) - queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", api, []*models.CloudWatchQuery{query}, tagValueCache, 50) - assert.Nil(t, err) - assert.Len(t, queries, 1) - // assert that the values was set to an empty array - assert.Equal(t, map[string][]string{"Test_DimensionName2": {}}, queries[0].Dimensions) - - // Confirm that it calls the api again if the last call did not return any values - api.Metrics = []*cloudwatch.Metric{ - {MetricName: utils.Pointer("Test_MetricName"), Dimensions: []*cloudwatch.Dimension{{Name: utils.Pointer("Test_DimensionName2"), Value: utils.Pointer("Value")}}}, - } - api.On("ListMetricsPagesWithContext").Return(nil) - queries, err = executor.getDimensionValuesForWildcards(ctx, "us-east-1", api, []*models.CloudWatchQuery{query}, tagValueCache, 50) - assert.Nil(t, err) - assert.Len(t, queries, 1) - assert.Equal(t, map[string][]string{"Test_DimensionName2": {"Value"}}, queries[0].Dimensions) - api.AssertExpectations(t) + queries, err := executor.getDimensionValuesForWildcards(ctx, "us-east-1", nil, []*models.CloudWatchQuery{query}, cache.New(0, 0), 50, noSkip) + assert.Nil(t, err) + assert.Len(t, queries, 1) + assert.Equal(t, map[string][]string{}, queries[0].Dimensions) + }) }) } diff --git a/pkg/tsdb/cloudwatch/models/cloudwatch_query.go b/pkg/tsdb/cloudwatch/models/cloudwatch_query.go index 4d6afa1e9e7..56709d8442f 100644 --- a/pkg/tsdb/cloudwatch/models/cloudwatch_query.go +++ b/pkg/tsdb/cloudwatch/models/cloudwatch_query.go @@ -50,6 +50,16 @@ const ( chinaConsoleURL = "console.amazonaws.cn" ) +type SQLExpressionGroupBy struct { + Expressions []dataquery.QueryEditorGroupByExpression `json:"expressions"` + Type dataquery.QueryEditorArrayExpressionType `json:"type"` +} + +type sqlExpression struct { + dataquery.SQLExpression + GroupBy *SQLExpressionGroupBy `json:"groupBy,omitempty"` +} + type CloudWatchQuery struct { logger log.Logger RefId string @@ -59,6 +69,7 @@ type CloudWatchQuery struct { MetricName string Statistic string Expression string + Sql sqlExpression SqlExpression string ReturnData bool Dimensions map[string][]string @@ -210,8 +221,9 @@ var validMetricDataID = regexp.MustCompile(`^[a-z][a-zA-Z0-9_]*$`) type metricsDataQuery struct { dataquery.CloudWatchMetricsQuery - Type string `json:"type"` - TimezoneUTCOffset string `json:"timezoneUTCOffset"` + Sql *sqlExpression `json:"sql,omitempty"` + Type string `json:"type"` + TimezoneUTCOffset string `json:"timezoneUTCOffset"` } // ParseMetricDataQueries decodes the metric data queries json, validates, sets default values and returns an array of CloudWatchQueries. @@ -254,6 +266,10 @@ func ParseMetricDataQueries(dataQueries []backend.DataQuery, startTime time.Time cwQuery.MetricQueryType = *mdq.MetricQueryType } + if mdq.Sql != nil { + cwQuery.Sql = *mdq.Sql + } + if mdq.SqlExpression != nil { cwQuery.SqlExpression = *mdq.SqlExpression } diff --git a/pkg/tsdb/cloudwatch/response_parser.go b/pkg/tsdb/cloudwatch/response_parser.go index f8953021a1f..2feaf906c2c 100644 --- a/pkg/tsdb/cloudwatch/response_parser.go +++ b/pkg/tsdb/cloudwatch/response_parser.go @@ -114,17 +114,28 @@ func parseLabels(cloudwatchLabel string, query *models.CloudWatchQuery) (string, return splitLabels[0], labels } -func getLabels(cloudwatchLabel string, query *models.CloudWatchQuery) data.Labels { +func getLabels(cloudwatchLabel string, query *models.CloudWatchQuery, addSeriesLabelAsFallback bool) data.Labels { dims := make([]string, 0, len(query.Dimensions)) for k := range query.Dimensions { dims = append(dims, k) } sort.Strings(dims) labels := data.Labels{} + + if addSeriesLabelAsFallback { + labels["Series"] = cloudwatchLabel + } + for _, dim := range dims { values := query.Dimensions[dim] if len(values) == 1 && values[0] != "*" { labels[dim] = values[0] + } else if len(values) == 0 { + // Metric Insights metrics might not have a value for a dimension specified in the `GROUP BY` clause for Metric Query type queries. When this happens, CloudWatch returns "Other" in the label for the dimension so `len(values)` would be 0. + // We manually add "Other" as the value for the dimension to match what CloudWatch returns in the label. + // See the note under `GROUP BY` in https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-metrics-insights-querylanguage.html + labels[dim] = "Other" + continue } else { for _, value := range values { if value == cloudwatchLabel || value == "*" { @@ -195,10 +206,12 @@ func buildDataFrames(ctx context.Context, startTime time.Time, endTime time.Time name := label var labels data.Labels - if features.IsEnabled(ctx, features.FlagCloudWatchNewLabelParsing) { + if query.GetGetMetricDataAPIMode() == models.GMDApiModeSQLExpression { + labels = getLabels(label, query, true) + } else if features.IsEnabled(ctx, features.FlagCloudWatchNewLabelParsing) { name, labels = parseLabels(label, query) } else { - labels = getLabels(label, query) + labels = getLabels(label, query, false) } timestamps := []*time.Time{} points := []*float64{} diff --git a/pkg/tsdb/cloudwatch/response_parser_test.go b/pkg/tsdb/cloudwatch/response_parser_test.go index bd992d17f10..be1d28107ce 100644 --- a/pkg/tsdb/cloudwatch/response_parser_test.go +++ b/pkg/tsdb/cloudwatch/response_parser_test.go @@ -363,7 +363,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) { assert.Equal(t, "res", frames[1].Fields[1].Labels["Resource"]) }) - t.Run("when not using multi-value dimension filters", func(t *testing.T) { + t.Run("when not using multi-value dimension filters on a `MetricSearch` query", func(t *testing.T) { timestamp := time.Unix(0, 0) response := &models.QueryRowResponse{ Metrics: []*cloudwatch.MetricDataResult{ @@ -391,7 +391,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) { }, Statistic: "Average", Period: 60, - MetricQueryType: models.MetricQueryTypeQuery, + MetricQueryType: models.MetricQueryTypeSearch, MetricEditorMode: models.MetricEditorModeRaw, } frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query) @@ -403,7 +403,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) { assert.Equal(t, "res", frames[0].Fields[1].Labels["Resource"]) }) - t.Run("when non-static label set on query", func(t *testing.T) { + t.Run("when non-static label set on a `MetricSearch` query", func(t *testing.T) { timestamp := time.Unix(0, 0) response := &models.QueryRowResponse{ Metrics: []*cloudwatch.MetricDataResult{ @@ -431,7 +431,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) { }, Statistic: "Average", Period: 60, - MetricQueryType: models.MetricQueryTypeQuery, + MetricQueryType: models.MetricQueryTypeSearch, MetricEditorMode: models.MetricEditorModeBuilder, Label: "set ${AVG} label", } @@ -444,7 +444,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) { assert.Equal(t, "res", frames[0].Fields[1].Labels["Resource"]) }) - t.Run("when static label set on query", func(t *testing.T) { + t.Run("when static label set on a `MetricSearch` query", func(t *testing.T) { timestamp := time.Unix(0, 0) response := &models.QueryRowResponse{ Metrics: []*cloudwatch.MetricDataResult{ @@ -472,7 +472,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) { }, Statistic: "Average", Period: 60, - MetricQueryType: models.MetricQueryTypeQuery, + MetricQueryType: models.MetricQueryTypeSearch, MetricEditorMode: models.MetricEditorModeBuilder, Label: "actual", } @@ -485,6 +485,84 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) { assert.Equal(t, "res", frames[0].Fields[1].Labels["Resource"]) }) + t.Run("when `MetricQuery` query has no label set and `GROUP BY` clause has multiple fields", func(t *testing.T) { + timestamp := time.Unix(0, 0) + response := &models.QueryRowResponse{ + Metrics: []*cloudwatch.MetricDataResult{ + { + Id: aws.String("query1"), + Label: aws.String("EC2 vCPU"), + Timestamps: []*time.Time{ + aws.Time(timestamp), + }, + Values: []*float64{aws.Float64(23)}, + StatusCode: aws.String("Complete"), + }, + { + Id: aws.String("query2"), + Label: aws.String("Elastic Loading Balancing ApplicationLoadBalancersPerRegion"), + Timestamps: []*time.Time{ + aws.Time(timestamp), + }, + Values: []*float64{aws.Float64(23)}, + StatusCode: aws.String("Complete"), + }, + }, + } + + query := &models.CloudWatchQuery{ + RefId: "refId1", + Region: "us-east-1", + Statistic: "Average", + Period: 60, + MetricQueryType: models.MetricQueryTypeQuery, + MetricEditorMode: models.MetricEditorModeBuilder, + Dimensions: map[string][]string{"Service": {"EC2", "Elastic Loading Balancing"}, "Resource": {"vCPU", "ApplicationLoadBalancersPerRegion"}}, + SqlExpression: "SELECT AVG(ResourceCount) FROM SCHEMA(\"AWS/Usage\", Class, Resource, Service, Type) GROUP BY Service, Resource", + } + frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query) + require.NoError(t, err) + + assert.Equal(t, "EC2 vCPU", frames[0].Name) + assert.Equal(t, "EC2", frames[0].Fields[1].Labels["Service"]) + assert.Equal(t, "vCPU", frames[0].Fields[1].Labels["Resource"]) + assert.Equal(t, "Elastic Loading Balancing ApplicationLoadBalancersPerRegion", frames[1].Name) + assert.Equal(t, "Elastic Loading Balancing", frames[1].Fields[1].Labels["Service"]) + assert.Equal(t, "ApplicationLoadBalancersPerRegion", frames[1].Fields[1].Labels["Resource"]) + }) + + t.Run("when `MetricQuery` query has no `GROUP BY` clause", func(t *testing.T) { + timestamp := time.Unix(0, 0) + response := &models.QueryRowResponse{ + Metrics: []*cloudwatch.MetricDataResult{ + { + Id: aws.String("query1"), + Label: aws.String("cloudwatch-default-label"), + Timestamps: []*time.Time{ + aws.Time(timestamp), + }, + Values: []*float64{aws.Float64(23)}, + StatusCode: aws.String("Complete"), + }, + }, + } + + query := &models.CloudWatchQuery{ + RefId: "refId1", + Region: "us-east-1", + Statistic: "Average", + Period: 60, + MetricQueryType: models.MetricQueryTypeQuery, + MetricEditorMode: models.MetricEditorModeBuilder, + SqlExpression: "SELECT AVG(ResourceCount) FROM SCHEMA(\"AWS/Usage\", Class, Resource, Service, Type)", + } + frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query) + require.NoError(t, err) + + assert.Equal(t, "cloudwatch-default-label", frames[0].Name) + assert.Equal(t, "cloudwatch-default-label", frames[0].Fields[1].Labels["Series"]) + }) + t.Run("Parse cloudwatch response", func(t *testing.T) { timestamp := time.Unix(0, 0) response := &models.QueryRowResponse{ diff --git a/pkg/tsdb/cloudwatch/time_series_query.go b/pkg/tsdb/cloudwatch/time_series_query.go index 6b5777fc79e..e7d09de8ef0 100644 --- a/pkg/tsdb/cloudwatch/time_series_query.go +++ b/pkg/tsdb/cloudwatch/time_series_query.go @@ -96,11 +96,20 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, req *ba return err } - if !features.IsEnabled(ctx, features.FlagCloudWatchNewLabelParsing) { - requestQueries, err = e.getDimensionValuesForWildcards(ctx, region, client, requestQueries, instance.tagValueCache, instance.Settings.GrafanaSettings.ListMetricsPageLimit) - if err != nil { - return err + newLabelParsingEnabled := features.IsEnabled(ctx, features.FlagCloudWatchNewLabelParsing) + requestQueries, err = e.getDimensionValuesForWildcards(ctx, region, client, requestQueries, instance.tagValueCache, instance.Settings.GrafanaSettings.ListMetricsPageLimit, func(q *models.CloudWatchQuery) bool { + if q.MetricQueryType == models.MetricQueryTypeSearch && (q.MatchExact || newLabelParsingEnabled) { + return true } + + if q.MetricQueryType == models.MetricQueryTypeQuery && q.MetricEditorMode == models.MetricEditorModeRaw { + return true + } + + return false + }) + if err != nil { + return err } res, err := e.parseResponse(ctx, startTime, endTime, mdo, requestQueries) diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/utils.ts b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/utils.ts index b1d72e0e6cf..b25dd6fa45e 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/utils.ts +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/SQLBuilderEditor/utils.ts @@ -224,13 +224,16 @@ export function setMetricName(query: CloudWatchMetricsQuery, metricName: string) name: metricName, }; - return setSql(query, { - select: { - type: QueryEditorExpressionType.Function, - ...(query.sql?.select ?? {}), - parameters: [param], - }, - }); + return setSql( + { ...query, metricName }, + { + select: { + type: QueryEditorExpressionType.Function, + ...(query.sql?.select ?? {}), + parameters: [param], + }, + } + ); } export function removeMetricName(query: CloudWatchMetricsQuery): CloudWatchMetricsQuery {
onEdit(idx)}>   {link.type} - + onEdit(idx)}> + {link.title && {link.title}} {link.type === 'link' && {link.url}} {link.type === 'dashboards' && } - + {idx !== 0 && ( From ed89354eaa6091245e33b5c50029e471e73b527b Mon Sep 17 00:00:00 2001 From: Timur Olzhabayev Date: Thu, 25 Apr 2024 12:53:10 +0200 Subject: [PATCH 105/222] Chore: Adding debug logging to signature checks (#86915) Adding debug logging to signature checks --- pkg/plugins/manager/signature/manifest.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/plugins/manager/signature/manifest.go b/pkg/plugins/manager/signature/manifest.go index f2c64917f31..4bb035f011e 100644 --- a/pkg/plugins/manager/signature/manifest.go +++ b/pkg/plugins/manager/signature/manifest.go @@ -169,6 +169,7 @@ func (s *Signature) Calculate(ctx context.Context, src plugins.PluginSource, plu // Make sure the versions all match if manifest.Plugin != plugin.JSONData.ID || manifest.Version != plugin.JSONData.Info.Version { + s.log.Debug("Plugin signature invalid because ID or Version mismatch", "pluginId", plugin.JSONData.ID, "manifestPluginId", manifest.Plugin, "pluginVersion", plugin.JSONData.Info.Version, "manifestPluginVersion", manifest.Version) return plugins.Signature{ Status: plugins.SignatureStatusModified, }, nil @@ -194,6 +195,7 @@ func (s *Signature) Calculate(ctx context.Context, src plugins.PluginSource, plu for p, hash := range manifest.Files { err = verifyHash(s.log, plugin, p, hash) if err != nil { + s.log.Debug("Plugin signature invalid", "pluginId", plugin.JSONData.ID, "error", err) return plugins.Signature{ Status: plugins.SignatureStatusModified, }, nil @@ -259,7 +261,7 @@ func verifyHash(mlog log.Logger, plugin plugins.FoundPlugin, path, hash string) h := sha256.New() if _, err := io.Copy(h, f); err != nil { - return errors.New("could not calculate plugin file checksum") + return fmt.Errorf("could not calculate plugin file checksum. Path: %s. Error: %w", path, err) } sum := hex.EncodeToString(h.Sum(nil)) if sum != hash { From cd724d74aad60819b6ae5a14315939244fa4b8ca Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Thu, 25 Apr 2024 12:54:36 +0200 Subject: [PATCH 106/222] Authn: move namespace id type (#86853) * Use RoleType from org package * Move to identity package and re-export from authn * Replace usage of top level functions for identity Co-authored-by: Misi --- pkg/services/auth/identity/error.go | 13 ++ pkg/services/auth/identity/namespace.go | 113 ++++++++++++++++++ pkg/services/auth/identity/requester.go | 13 -- pkg/services/authn/authn.go | 2 +- pkg/services/authn/authnimpl/service.go | 2 +- pkg/services/authn/authnimpl/service_test.go | 2 +- .../authnimpl/sync/oauth_token_sync_test.go | 5 +- .../authn/authnimpl/sync/rbac_sync_test.go | 3 +- .../authn/authnimpl/sync/user_sync.go | 23 ++-- pkg/services/authn/clients/api_key.go | 15 ++- pkg/services/authn/clients/api_key_test.go | 4 +- pkg/services/authn/clients/oauth.go | 2 +- pkg/services/authn/clients/proxy.go | 8 +- pkg/services/authn/error.go | 1 - pkg/services/authn/identity.go | 13 +- pkg/services/authn/namespace.go | 106 ++-------------- 16 files changed, 170 insertions(+), 155 deletions(-) create mode 100644 pkg/services/auth/identity/error.go create mode 100644 pkg/services/auth/identity/namespace.go diff --git a/pkg/services/auth/identity/error.go b/pkg/services/auth/identity/error.go new file mode 100644 index 00000000000..1637af46402 --- /dev/null +++ b/pkg/services/auth/identity/error.go @@ -0,0 +1,13 @@ +package identity + +import ( + "errors" + + "github.com/grafana/grafana/pkg/util/errutil" +) + +var ( + ErrInvalidNamespaceID = errutil.BadRequest("auth.identity.invalid-namespace-id") + ErrNotIntIdentifier = errors.New("identifier is not an int64") + ErrIdentifierNotInitialized = errors.New("identifier is not initialized") +) diff --git a/pkg/services/auth/identity/namespace.go b/pkg/services/auth/identity/namespace.go new file mode 100644 index 00000000000..bcceffd8c8b --- /dev/null +++ b/pkg/services/auth/identity/namespace.go @@ -0,0 +1,113 @@ +package identity + +import ( + "fmt" + "strconv" + "strings" +) + +const ( + NamespaceUser = "user" + NamespaceAPIKey = "api-key" + NamespaceServiceAccount = "service-account" + NamespaceAnonymous = "anonymous" + NamespaceRenderService = "render" + NamespaceAccessPolicy = "access-policy" +) + +var AnonymousNamespaceID = MustNewNamespaceID(NamespaceAnonymous, 0) + +var namespaceLookup = map[string]struct{}{ + NamespaceUser: {}, + NamespaceAPIKey: {}, + NamespaceServiceAccount: {}, + NamespaceAnonymous: {}, + NamespaceRenderService: {}, + NamespaceAccessPolicy: {}, +} + +func ParseNamespaceID(str string) (NamespaceID, error) { + var namespaceID NamespaceID + + parts := strings.Split(str, ":") + if len(parts) != 2 { + return namespaceID, ErrInvalidNamespaceID.Errorf("expected namespace id to have 2 parts") + } + + namespace, id := parts[0], parts[1] + + if _, ok := namespaceLookup[namespace]; !ok { + return namespaceID, ErrInvalidNamespaceID.Errorf("got invalid namespace %s", namespace) + } + + namespaceID.id = id + namespaceID.namespace = namespace + + return namespaceID, nil +} + +// MustParseNamespaceID parses namespace id, it will panic if it fails to do so. +// Suitable to use in tests or when we can guarantee that we pass a correct format. +func MustParseNamespaceID(str string) NamespaceID { + namespaceID, err := ParseNamespaceID(str) + if err != nil { + panic(err) + } + return namespaceID +} + +// NewNamespaceID creates a new NamespaceID, will fail for invalid namespace. +func NewNamespaceID(namespace string, id int64) (NamespaceID, error) { + var namespaceID NamespaceID + if _, ok := namespaceLookup[namespace]; !ok { + return namespaceID, ErrInvalidNamespaceID.Errorf("got invalid namespace %s", namespace) + } + namespaceID.id = strconv.FormatInt(id, 10) + namespaceID.namespace = namespace + return namespaceID, nil +} + +// MustNewNamespaceID creates a new NamespaceID, will panic for invalid namespace. +// Suitable to use in tests or when we can guarantee that we pass a correct format. +func MustNewNamespaceID(namespace string, id int64) NamespaceID { + namespaceID, err := NewNamespaceID(namespace, id) + if err != nil { + panic(err) + } + return namespaceID +} + +// NewNamespaceIDUnchecked creates a new NamespaceID without checking if namespace is valid. +// It us up to the caller to ensure that namespace is valid. +func NewNamespaceIDUnchecked(namespace string, id int64) NamespaceID { + return NamespaceID{ + id: strconv.FormatInt(id, 10), + namespace: namespace, + } +} + +// FIXME: use this instead of encoded string through the codebase +type NamespaceID struct { + id string + namespace string +} + +func (ni NamespaceID) ID() string { + return ni.id +} + +func (ni NamespaceID) ParseInt() (int64, error) { + return strconv.ParseInt(ni.id, 10, 64) +} + +func (ni NamespaceID) Namespace() string { + return ni.namespace +} + +func (ni NamespaceID) IsNamespace(expected ...string) bool { + return IsNamespace(ni.namespace, expected...) +} + +func (ni NamespaceID) String() string { + return fmt.Sprintf("%s:%s", ni.namespace, ni.id) +} diff --git a/pkg/services/auth/identity/requester.go b/pkg/services/auth/identity/requester.go index 57c82e9ecd9..87140b85d4f 100644 --- a/pkg/services/auth/identity/requester.go +++ b/pkg/services/auth/identity/requester.go @@ -1,25 +1,12 @@ package identity import ( - "errors" "fmt" "strconv" "github.com/grafana/grafana/pkg/models/roletype" ) -const ( - NamespaceUser = "user" - NamespaceAPIKey = "api-key" - NamespaceServiceAccount = "service-account" - NamespaceAnonymous = "anonymous" - NamespaceRenderService = "render" - NamespaceAccessPolicy = "access-policy" -) - -var ErrNotIntIdentifier = errors.New("identifier is not an int64") -var ErrIdentifierNotInitialized = errors.New("identifier is not initialized") - type Requester interface { // GetID returns namespaced id for the entity GetID() string diff --git a/pkg/services/authn/authn.go b/pkg/services/authn/authn.go index c2ec983b72a..83cbc622bf9 100644 --- a/pkg/services/authn/authn.go +++ b/pkg/services/authn/authn.go @@ -153,7 +153,7 @@ type RedirectClient interface { // that should happen during logout and supports client specific redirect URL. type LogoutClient interface { Client - Logout(ctx context.Context, user identity.Requester) (*Redirect, bool) + Logout(ctx context.Context, user Requester) (*Redirect, bool) } type PasswordClient interface { diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 880f6d2331b..2b12e1021f6 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -260,7 +260,7 @@ func (s *Service) RedirectURL(ctx context.Context, client string, r *authn.Reque return redirectClient.RedirectURL(ctx, r) } -func (s *Service) Logout(ctx context.Context, user identity.Requester, sessionToken *auth.UserToken) (*authn.Redirect, error) { +func (s *Service) Logout(ctx context.Context, user authn.Requester, sessionToken *auth.UserToken) (*authn.Redirect, error) { ctx, span := s.tracer.Start(ctx, "authn.Logout") defer span.End() diff --git a/pkg/services/authn/authnimpl/service_test.go b/pkg/services/authn/authnimpl/service_test.go index 57f05217c23..dd2592f89d7 100644 --- a/pkg/services/authn/authnimpl/service_test.go +++ b/pkg/services/authn/authnimpl/service_test.go @@ -488,7 +488,7 @@ func TestService_ResolveIdentity(t *testing.T) { t.Run("should return error for for unknown namespace", func(t *testing.T) { svc := setupTests(t) _, err := svc.ResolveIdentity(context.Background(), 1, "some:1") - assert.ErrorIs(t, err, authn.ErrInvalidNamepsaceID) + assert.ErrorIs(t, err, authn.ErrInvalidNamespaceID) }) t.Run("should return error for for namespace that don't have a resolver", func(t *testing.T) { diff --git a/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go b/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go index 16b69dfc071..56145f55804 100644 --- a/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go @@ -14,7 +14,6 @@ import ( "github.com/grafana/grafana/pkg/login/social/socialtest" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth/authtest" - "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/oauthtoken/oauthtokentest" @@ -92,7 +91,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) { ) service := &oauthtokentest.MockOauthTokenService{ - HasOAuthEntryFunc: func(ctx context.Context, usr identity.Requester) (*login.UserAuth, bool, error) { + HasOAuthEntryFunc: func(ctx context.Context, usr authn.Requester) (*login.UserAuth, bool, error) { hasEntryCalled = true return tt.expectedHasEntryToken, tt.expectedHasEntryToken != nil, nil }, @@ -100,7 +99,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) { invalidateTokensCalled = true return nil }, - TryTokenRefreshFunc: func(ctx context.Context, usr identity.Requester) error { + TryTokenRefreshFunc: func(ctx context.Context, usr authn.Requester) error { tryRefreshCalled = true return tt.expectedTryRefreshErr }, diff --git a/pkg/services/authn/authnimpl/sync/rbac_sync_test.go b/pkg/services/authn/authnimpl/sync/rbac_sync_test.go index 202dd58cadf..a29c4c7c814 100644 --- a/pkg/services/authn/authnimpl/sync/rbac_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/rbac_sync_test.go @@ -7,7 +7,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" - "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -143,7 +142,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { func setupTestEnv() *RBACSync { acMock := &acmock.Mock{ - GetUserPermissionsFunc: func(ctx context.Context, siu identity.Requester, o accesscontrol.Options) ([]accesscontrol.Permission, error) { + GetUserPermissionsFunc: func(ctx context.Context, siu authn.Requester, o accesscontrol.Options) ([]accesscontrol.Permission, error) { return []accesscontrol.Permission{ {Action: accesscontrol.ActionUsersRead}, }, nil diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index eb09115d580..864e0d57168 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -4,10 +4,8 @@ import ( "context" "errors" "fmt" - "strconv" "github.com/grafana/grafana/pkg/infra/log" - authidentity "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -112,14 +110,13 @@ func (s *UserSync) FetchSyncedUserHook(ctx context.Context, identity *authn.Iden return nil } - namespace, id := identity.GetNamespacedID() - if !authidentity.IsNamespace(namespace, authn.NamespaceUser, authn.NamespaceServiceAccount) { + if !identity.ID.IsNamespace(authn.NamespaceUser, authn.NamespaceServiceAccount) { return nil } - userID, err := strconv.ParseInt(id, 10, 64) + userID, err := identity.ID.ParseInt() if err != nil { - s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id, "err", err) + s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err) return nil } @@ -151,14 +148,13 @@ func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identit return nil } - namespace, id := identity.GetNamespacedID() - if namespace != authn.NamespaceUser && namespace != authn.NamespaceServiceAccount { + if !identity.ID.IsNamespace(authn.NamespaceUser, authn.NamespaceServiceAccount) { return nil } - userID, err := authidentity.IntIdentifier(namespace, id) + userID, err := identity.ID.ParseInt() if err != nil { - s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id, "err", err) + s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err) return nil } @@ -184,14 +180,13 @@ func (s *UserSync) EnableUserHook(ctx context.Context, identity *authn.Identity, return nil } - namespace, id := identity.GetNamespacedID() - if namespace != authn.NamespaceUser { + if !identity.ID.IsNamespace(authn.NamespaceUser) { return nil } - userID, err := authidentity.IntIdentifier(namespace, id) + userID, err := identity.ID.ParseInt() if err != nil { - s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id, "err", err) + s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err) return nil } diff --git a/pkg/services/authn/clients/api_key.go b/pkg/services/authn/clients/api_key.go index 4b36922cf99..d01e33e5766 100644 --- a/pkg/services/authn/clients/api_key.go +++ b/pkg/services/authn/clients/api_key.go @@ -10,7 +10,6 @@ import ( "github.com/grafana/grafana/pkg/components/satokengen" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/apikey" - authidentity "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -141,7 +140,7 @@ func (s *APIKey) Namespace() string { func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) { if !namespaceID.IsNamespace(authn.NamespaceAPIKey) { - return nil, authn.ErrInvalidNamepsaceID.Errorf("got unspected namespace: %s", namespaceID.Namespace()) + return nil, authn.ErrInvalidNamespaceID.Errorf("got unspected namespace: %s", namespaceID.Namespace()) } apiKeyID, err := namespaceID.ParseInt() @@ -161,7 +160,7 @@ func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID a } if key.ServiceAccountId != nil && *key.ServiceAccountId >= 1 { - return nil, authn.ErrInvalidNamepsaceID.Errorf("api key belongs to service account") + return nil, authn.ErrInvalidNamespaceID.Errorf("api key belongs to service account") } return newAPIKeyIdentity(key), nil @@ -189,18 +188,17 @@ func (s *APIKey) Hook(ctx context.Context, identity *authn.Identity, r *authn.Re } func (s *APIKey) getAPIKeyID(ctx context.Context, identity *authn.Identity, r *authn.Request) (apiKeyID int64, exists bool) { - namespace, identifier := identity.GetNamespacedID() - - id, err := authidentity.IntIdentifier(namespace, identifier) + id, err := identity.ID.ParseInt() if err != nil { s.log.Warn("Failed to parse ID from identifier", "err", err) return -1, false } - if namespace == authn.NamespaceAPIKey { + + if identity.ID.IsNamespace(authn.NamespaceAPIKey) { return id, true } - if namespace == authn.NamespaceServiceAccount { + if identity.ID.IsNamespace(authn.NamespaceServiceAccount) { // When the identity is service account, the ID in from the namespace is the service account ID. // We need to fetch the API key in this scenario, as we could use it to uniquely identify a service account token. apiKey, err := s.getAPIKey(ctx, getTokenFromRequest(r)) @@ -211,6 +209,7 @@ func (s *APIKey) getAPIKeyID(ctx context.Context, identity *authn.Identity, r *a return apiKey.ID, true } + return -1, false } diff --git a/pkg/services/authn/clients/api_key_test.go b/pkg/services/authn/clients/api_key_test.go index c67c11b6cf0..d52289c643f 100644 --- a/pkg/services/authn/clients/api_key_test.go +++ b/pkg/services/authn/clients/api_key_test.go @@ -298,7 +298,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) { { desc: "should return error for invalid namespace", namespaceID: authn.MustParseNamespaceID("user:1"), - expectedErr: authn.ErrInvalidNamepsaceID, + expectedErr: authn.ErrInvalidNamespaceID, }, { desc: "should return error when api key has expired", @@ -328,7 +328,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) { OrgID: 1, ServiceAccountId: intPtr(1), }, - expectedErr: authn.ErrInvalidNamepsaceID, + expectedErr: authn.ErrInvalidNamespaceID, }, { desc: "should return error when api key is belongs to different org", diff --git a/pkg/services/authn/clients/oauth.go b/pkg/services/authn/clients/oauth.go index 0a446102780..318e732971f 100644 --- a/pkg/services/authn/clients/oauth.go +++ b/pkg/services/authn/clients/oauth.go @@ -250,7 +250,7 @@ func (c *OAuth) RedirectURL(ctx context.Context, r *authn.Request) (*authn.Redir }, nil } -func (c *OAuth) Logout(ctx context.Context, user identity.Requester) (*authn.Redirect, bool) { +func (c *OAuth) Logout(ctx context.Context, user authn.Requester) (*authn.Redirect, bool) { token := c.oauthService.GetCurrentOAuthToken(ctx, user) namespace, id := user.GetNamespacedID() diff --git a/pkg/services/authn/clients/proxy.go b/pkg/services/authn/clients/proxy.go index bbe96491533..92ebf0c2250 100644 --- a/pkg/services/authn/clients/proxy.go +++ b/pkg/services/authn/clients/proxy.go @@ -14,7 +14,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/remotecache" - authidentity "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/setting" @@ -150,14 +149,13 @@ func (c *Proxy) Hook(ctx context.Context, identity *authn.Identity, r *authn.Req return nil } - namespace, identifier := identity.GetNamespacedID() - if namespace != authn.NamespaceUser { + if !identity.ID.IsNamespace(authn.NamespaceUser) { return nil } - id, err := authidentity.IntIdentifier(namespace, identifier) + id, err := identity.ID.ParseInt() if err != nil { - c.log.Warn("Failed to cache proxy user", "error", err, "userId", identifier, "err", err) + c.log.Warn("Failed to cache proxy user", "error", err, "userId", identity.ID.ID(), "err", err) return nil } diff --git a/pkg/services/authn/error.go b/pkg/services/authn/error.go index 5420ccd19ad..053ceeacfab 100644 --- a/pkg/services/authn/error.go +++ b/pkg/services/authn/error.go @@ -8,5 +8,4 @@ var ( ErrClientNotConfigured = errutil.BadRequest("auth.client.notConfigured") ErrUnsupportedIdentity = errutil.NotImplemented("auth.identity.unsupported") ErrExpiredAccessToken = errutil.Unauthorized("oauth.expired-token", errutil.WithPublicMessage("OAuth access token expired")) - ErrInvalidNamepsaceID = errutil.BadRequest("auth.identity.invalid-namespace-id") ) diff --git a/pkg/services/authn/identity.go b/pkg/services/authn/identity.go index 3f47c084c24..38d9a51c67b 100644 --- a/pkg/services/authn/identity.go +++ b/pkg/services/authn/identity.go @@ -7,7 +7,6 @@ import ( "golang.org/x/oauth2" - "github.com/grafana/grafana/pkg/models/roletype" "github.com/grafana/grafana/pkg/models/usertoken" "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/login" @@ -17,7 +16,9 @@ import ( const GlobalOrgID = int64(0) -var _ identity.Requester = (*Identity)(nil) +type Requester = identity.Requester + +var _ Requester = (*Identity)(nil) type Identity struct { // ID is the unique identifier for the entity in the Grafana database. @@ -131,13 +132,13 @@ func (i *Identity) GetOrgName() string { return i.OrgName } -func (i *Identity) GetOrgRole() roletype.RoleType { +func (i *Identity) GetOrgRole() org.RoleType { if i.OrgRoles == nil { - return roletype.RoleNone + return org.RoleNone } if i.OrgRoles[i.GetOrgID()] == "" { - return roletype.RoleNone + return org.RoleNone } return i.OrgRoles[i.GetOrgID()] @@ -172,7 +173,7 @@ func (i *Identity) GetTeams() []int64 { return i.Teams } -func (i *Identity) HasRole(role roletype.RoleType) bool { +func (i *Identity) HasRole(role org.RoleType) bool { if i.GetIsGrafanaAdmin() { return true } diff --git a/pkg/services/authn/namespace.go b/pkg/services/authn/namespace.go index a5324561963..09d71052cc7 100644 --- a/pkg/services/authn/namespace.go +++ b/pkg/services/authn/namespace.go @@ -1,10 +1,6 @@ package authn import ( - "fmt" - "strconv" - "strings" - "github.com/grafana/grafana/pkg/services/auth/identity" ) @@ -19,97 +15,13 @@ const ( var AnonymousNamespaceID = MustNewNamespaceID(NamespaceAnonymous, 0) -var namespaceLookup = map[string]struct{}{ - NamespaceUser: {}, - NamespaceAPIKey: {}, - NamespaceServiceAccount: {}, - NamespaceAnonymous: {}, - NamespaceRenderService: {}, - NamespaceAccessPolicy: {}, -} +type NamespaceID = identity.NamespaceID -func ParseNamespaceID(str string) (NamespaceID, error) { - var namespaceID NamespaceID - - parts := strings.Split(str, ":") - if len(parts) != 2 { - return namespaceID, ErrInvalidNamepsaceID.Errorf("expected namespace id to have 2 parts") - } - - namespace, id := parts[0], parts[1] - - if _, ok := namespaceLookup[namespace]; !ok { - return namespaceID, ErrInvalidNamepsaceID.Errorf("got invalid namespace %s", namespace) - } - - namespaceID.id = id - namespaceID.namespace = namespace - - return namespaceID, nil -} - -// MustParseNamespaceID parses namespace id, it will panic it failes to do so. -// Sutable to use in tests or when we can garantuee that we pass a correct format. -func MustParseNamespaceID(str string) NamespaceID { - namespaceID, err := ParseNamespaceID(str) - if err != nil { - panic(err) - } - return namespaceID -} - -// NewNamespaceID creates a new NamespaceID, will fail for invalid namespace. -func NewNamespaceID(namespace string, id int64) (NamespaceID, error) { - var namespaceID NamespaceID - if _, ok := namespaceLookup[namespace]; !ok { - return namespaceID, ErrInvalidNamepsaceID.Errorf("got invalid namespace %s", namespace) - } - namespaceID.id = strconv.FormatInt(id, 10) - namespaceID.namespace = namespace - return namespaceID, nil -} - -// MustNewNamespaceID creates a new NamespaceID, will panic for invalid namespace. -// Sutable to use in tests or when we can garantuee that we pass a correct format. -func MustNewNamespaceID(namespace string, id int64) NamespaceID { - namespaceID, err := NewNamespaceID(namespace, id) - if err != nil { - panic(err) - } - return namespaceID -} - -// NewNamespaceIDUnchecked creates a new NamespaceID without checking if namespace is valid. -// It us up to the caller to ensure that namespace is valid. -func NewNamespaceIDUnchecked(namespace string, id int64) NamespaceID { - return NamespaceID{ - id: strconv.FormatInt(id, 10), - namespace: namespace, - } -} - -// FIXME: use this instead of encoded string through the codebase -type NamespaceID struct { - id string - namespace string -} - -func (ni NamespaceID) ID() string { - return ni.id -} - -func (ni NamespaceID) ParseInt() (int64, error) { - return strconv.ParseInt(ni.id, 10, 64) -} - -func (ni NamespaceID) Namespace() string { - return ni.namespace -} - -func (ni NamespaceID) IsNamespace(expected ...string) bool { - return identity.IsNamespace(ni.namespace, expected...) -} - -func (ni NamespaceID) String() string { - return fmt.Sprintf("%s:%s", ni.namespace, ni.id) -} +var ( + ParseNamespaceID = identity.ParseNamespaceID + MustParseNamespaceID = identity.MustParseNamespaceID + NewNamespaceID = identity.NewNamespaceID + MustNewNamespaceID = identity.MustNewNamespaceID + NewNamespaceIDUnchecked = identity.NewNamespaceIDUnchecked + ErrInvalidNamespaceID = identity.ErrInvalidNamespaceID +) From e711d10925ff44b6a0256172e4c606bcd821b2bd Mon Sep 17 00:00:00 2001 From: Alexa V <239999+axelavargas@users.noreply.github.com> Date: Thu, 25 Apr 2024 13:06:57 +0200 Subject: [PATCH 107/222] Dashboard: Migration Fixes Mixed data source losing existing queries (#86883) --- .../panel-edit/PanelDataPane/PanelDataQueriesTab.tsx | 12 ++++++++++-- .../dashboard-scene/panel-edit/VizPanelManager.tsx | 5 +---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx index 1f47fdabe5a..324a7f9e232 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { CoreApp, DataSourceApi, DataSourceInstanceSettings, IconName } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { config } from '@grafana/runtime'; +import { config, getDataSourceSrv } from '@grafana/runtime'; import { SceneObjectBase, SceneComponentProps, sceneGraph, SceneQueryRunner } from '@grafana/scenes'; import { DataQuery } from '@grafana/schema'; import { Button, HorizontalGroup, Tab } from '@grafana/ui'; @@ -120,7 +120,15 @@ export class PanelDataQueriesTab extends SceneObjectBase { const { dsSettings, datasource } = this._panelManager.state; - const ds = !dsSettings?.meta.mixed ? dsSettings : datasource; + let ds; + if (!dsSettings?.meta.mixed) { + ds = dsSettings; // Use dsSettings if it is not mixed + } else if (!datasource?.meta.mixed) { + ds = datasource; // Use datasource if dsSettings is mixed but datasource is not + } else { + // Use default datasource if both are mixed or just datasource is mixed + ds = getDataSourceSrv().getInstanceSettings(config.defaultDatasource); + } return { ...datasource?.getDefaultQuery?.(CoreApp.PanelEditor), diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx index 0cfa0f7c5bc..a58b3860262 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.tsx @@ -141,10 +141,7 @@ export class VizPanelManager extends SceneObjectBase { } if (datasource && dsSettings) { - this.setState({ - datasource, - dsSettings, - }); + this.setState({ datasource, dsSettings }); storeLastUsedDataSourceInLocalStorage( { From f43762f39abad43f99b85cbcff6ca30c56f9d75f Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Thu, 25 Apr 2024 11:51:02 +0100 Subject: [PATCH 108/222] Remove (most) occurrences of `HorizontalGroup` within Alerting --- .betterer.results | 19 ++++--------------- .../components/receivers/GlobalConfigForm.tsx | 6 +++--- .../components/rule-editor/PreviewRule.tsx | 6 +++--- .../alert-rule-form/AlertRuleForm.tsx | 7 +++---- .../rules/MultipleDataSourcePicker.tsx | 6 +++--- .../unified/components/rules/RulesGroup.tsx | 6 +++--- 6 files changed, 19 insertions(+), 31 deletions(-) diff --git a/.betterer.results b/.betterer.results index 92b2bf70fc3..196f50c692f 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1712,9 +1712,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "16"], [0, 0, 0, "Styles should be written using objects.", "17"] ], - "public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/alerting/unified/components/receivers/PayloadEditor.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], @@ -1893,9 +1890,8 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "1"] ], "public/app/features/alerting/unified/components/rule-editor/PreviewRule.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"], + [0, 0, 0, "Styles should be written using objects.", "1"] ], "public/app/features/alerting/unified/components/rule-editor/PreviewRuleResult.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], @@ -1935,9 +1931,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] ], - "public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPolicyMatchers.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] @@ -2028,9 +2021,6 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "2"], [0, 0, 0, "Styles should be written using objects.", "3"] ], - "public/app/features/alerting/unified/components/rules/MultipleDataSourcePicker.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], @@ -2092,7 +2082,7 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "0"] ], "public/app/features/alerting/unified/components/rules/RulesGroup.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], + [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], [0, 0, 0, "Styles should be written using objects.", "2"], [0, 0, 0, "Styles should be written using objects.", "3"], @@ -2103,8 +2093,7 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "8"], [0, 0, 0, "Styles should be written using objects.", "9"], [0, 0, 0, "Styles should be written using objects.", "10"], - [0, 0, 0, "Styles should be written using objects.", "11"], - [0, 0, 0, "Styles should be written using objects.", "12"] + [0, 0, 0, "Styles should be written using objects.", "11"] ], "public/app/features/alerting/unified/components/rules/RulesTable.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], diff --git a/public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx b/public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx index e0c4975a04d..7974cf27a8a 100644 --- a/public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useForm, FormProvider } from 'react-hook-form'; -import { Alert, Button, HorizontalGroup, LinkButton } from '@grafana/ui'; +import { Alert, Button, Stack, LinkButton } from '@grafana/ui'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; import { useDispatch } from 'app/types'; @@ -87,7 +87,7 @@ export const GlobalConfigForm = ({ config, alertManagerSourceName }: Props) => { /> ))}
- + {!readOnly && ( <> {loading && ( @@ -106,7 +106,7 @@ export const GlobalConfigForm = ({ config, alertManagerSourceName }: Props) => { > Cancel - +
diff --git a/public/app/features/alerting/unified/components/rule-editor/PreviewRule.tsx b/public/app/features/alerting/unified/components/rule-editor/PreviewRule.tsx index dcb979db88c..aef426bc1e6 100644 --- a/public/app/features/alerting/unified/components/rule-editor/PreviewRule.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/PreviewRule.tsx @@ -6,7 +6,7 @@ import { takeWhile } from 'rxjs/operators'; import { dateTimeFormatISO, GrafanaTheme2, LoadingState } from '@grafana/data'; import { getDataSourceSrv } from '@grafana/runtime'; -import { Alert, Button, HorizontalGroup, useStyles2 } from '@grafana/ui'; +import { Alert, Button, Stack, useStyles2 } from '@grafana/ui'; import { previewAlertRule } from '../../api/preview'; import { useAlertQueriesStatus } from '../../hooks/useAlertQueriesStatus'; @@ -32,7 +32,7 @@ export function PreviewRule(): React.ReactElement | null { return (
- + {allDataSourcesAvailable && (
); diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index 6c1a1434f02..1753714d312 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -5,7 +5,7 @@ import { Link, useParams } from 'react-router-dom'; import { GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { Button, ConfirmModal, CustomScrollbar, HorizontalGroup, Spinner, Stack, useStyles2 } from '@grafana/ui'; +import { Button, ConfirmModal, CustomScrollbar, Spinner, Stack, useStyles2 } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { useAppNotification } from 'app/core/copy/appNotification'; import { contextSrv } from 'app/core/core'; @@ -184,7 +184,7 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { useEffect(() => setEvaluateEvery(evaluateEveryInForm), [evaluateEveryInForm]); const actionButtons = ( - + {existing && ( ) : null} - {existing && isCortexLokiOrRecordingRule(watch) && ( )} - + ); const isPaused = existing && isGrafanaRulerRule(existing.rule) && isGrafanaRulerRulePaused(existing.rule); diff --git a/public/app/features/alerting/unified/components/rules/MultipleDataSourcePicker.tsx b/public/app/features/alerting/unified/components/rules/MultipleDataSourcePicker.tsx index 119d0fc8ec0..72124081a5a 100644 --- a/public/app/features/alerting/unified/components/rules/MultipleDataSourcePicker.tsx +++ b/public/app/features/alerting/unified/components/rules/MultipleDataSourcePicker.tsx @@ -10,7 +10,7 @@ import { import { selectors } from '@grafana/e2e-selectors'; import { getDataSourceSrv, DataSourcePickerState, DataSourcePickerProps } from '@grafana/runtime'; import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; -import { ActionMeta, HorizontalGroup, PluginSignatureBadge, MultiSelect } from '@grafana/ui'; +import { ActionMeta, Stack, PluginSignatureBadge, MultiSelect } from '@grafana/ui'; import { isDataSourceManagingAlerts } from '../../utils/datasource'; @@ -168,9 +168,9 @@ export const MultipleDataSourcePicker = (props: MultipleDataSourcePickerProps) = getOptionLabel={(o) => { if (o.meta && isUnsignedPluginSignature(o.meta.signature) && o !== value) { return ( - + {o.label} - + ); } return o.label || ''; diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx index dab832d4298..2ca8d1ca3f9 100644 --- a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { Badge, ConfirmModal, HorizontalGroup, Icon, Spinner, Stack, Tooltip, useStyles2 } from '@grafana/ui'; +import { Badge, ConfirmModal, Icon, Spinner, Stack, Tooltip, useStyles2 } from '@grafana/ui'; import { useDispatch } from 'app/types'; import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; @@ -83,10 +83,10 @@ export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }: // for grafana, link to folder views if (isDeleting) { actionIcons.push( - + deleting - + ); } else if (rulesSource === GRAFANA_RULES_SOURCE_NAME) { if (folderUID) { From a68df4be880cb96dc46343c9eca30324eda114ec Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:43:37 +0200 Subject: [PATCH 109/222] Alerting: Skip flaky test (#86921) Skip flacky test --- .../app/features/alerting/unified/PanelAlertTabContent.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx b/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx index 9c70f018da8..bf2364f7e96 100644 --- a/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx +++ b/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx @@ -286,7 +286,7 @@ describe('PanelAlertTabContent', () => { }); }); - it('Will render alerts belonging to panel and a button to create alert from panel queries', async () => { + it.skip('Will render alerts belonging to panel and a button to create alert from panel queries', async () => { mocks.api.fetchRules.mockResolvedValue(rules); mocks.api.fetchRulerRules.mockResolvedValue(rulerRules); From 32215adb3711cb989d4432519bec6ae96d98d023 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Thu, 25 Apr 2024 08:58:25 -0400 Subject: [PATCH 110/222] Chore: Remove HorizontalGroup and VerticalGroup from storage (#86888) --- .betterer.results | 11 ++++------- public/app/features/storage/RootView.tsx | 22 +++++---------------- public/app/features/storage/StoragePage.tsx | 10 +++++----- 3 files changed, 14 insertions(+), 29 deletions(-) diff --git a/.betterer.results b/.betterer.results index 196f50c692f..12e65629126 100644 --- a/.betterer.results +++ b/.betterer.results @@ -4227,19 +4227,16 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "4"] ], "public/app/features/storage/RootView.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"] + [0, 0, 0, "Styles should be written using objects.", "0"], + [0, 0, 0, "Styles should be written using objects.", "1"] ], "public/app/features/storage/StoragePage.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], + [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], [0, 0, 0, "Styles should be written using objects.", "2"], [0, 0, 0, "Styles should be written using objects.", "3"], [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"] + [0, 0, 0, "Styles should be written using objects.", "5"] ], "public/app/features/storage/UploadButton.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] diff --git a/public/app/features/storage/RootView.tsx b/public/app/features/storage/RootView.tsx index 62bda7bb4e1..c6f855a7024 100644 --- a/public/app/features/storage/RootView.tsx +++ b/public/app/features/storage/RootView.tsx @@ -3,19 +3,7 @@ import React, { useMemo, useState } from 'react'; import { useAsync } from 'react-use'; import { DataFrame, GrafanaTheme2 } from '@grafana/data'; -import { - Alert, - Button, - Card, - FilterInput, - HorizontalGroup, - Icon, - IconName, - TagList, - useStyles2, - VerticalGroup, - InlineField, -} from '@grafana/ui'; +import { Alert, Button, Card, FilterInput, Icon, IconName, TagList, useStyles2, Stack, InlineField } from '@grafana/ui'; import { getGrafanaStorage } from './storage'; import { StorageInfo, StorageView } from './types'; @@ -62,7 +50,7 @@ export function RootView({ root, onPathChange }: Props) { const renderRoots = (pfix: string, roots: StorageInfo[]) => { return ( - + {roots.map((s) => { const ok = s.ready; return ( @@ -75,9 +63,9 @@ export function RootView({ root, onPathChange }: Props) { {s.notice?.map((notice) => )} - + - + @@ -85,7 +73,7 @@ export function RootView({ root, onPathChange }: Props) { ); })} - + ); }; diff --git a/public/app/features/storage/StoragePage.tsx b/public/app/features/storage/StoragePage.tsx index dc7be8568f6..8875241987c 100644 --- a/public/app/features/storage/StoragePage.tsx +++ b/public/app/features/storage/StoragePage.tsx @@ -4,7 +4,7 @@ import { useAsync } from 'react-use'; import { DataFrame, GrafanaTheme2, isDataFrame, ValueLinkConfig } from '@grafana/data'; import { locationService } from '@grafana/runtime'; -import { useStyles2, Spinner, TabsBar, Tab, Button, HorizontalGroup, Alert, toIconName } from '@grafana/ui'; +import { useStyles2, Spinner, TabsBar, Tab, Button, Stack, Box, Alert, toIconName } from '@grafana/ui'; import appEvents from 'app/core/app_events'; import { Page } from 'app/core/components/Page/Page'; import { useNavModel } from 'app/core/hooks/useNavModel'; @@ -163,9 +163,9 @@ export default function StoragePage(props: Props) { return (
- + - + {canAddFolder && ( <> @@ -200,8 +200,8 @@ export default function StoragePage(props: Props) { Delete )} - - + + {errorMessages.length > 0 && getErrorMessages()} From 262cb8213279f12c92aa2114190cf0225d0b1ac5 Mon Sep 17 00:00:00 2001 From: Imma Valls Date: Thu, 25 Apr 2024 15:09:38 +0200 Subject: [PATCH 111/222] Explore: add links to metrics and correlations editor to Explore (#86474) add links to metrics and correlations editor --- docs/sources/explore/_index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/explore/_index.md b/docs/sources/explore/_index.md index 2d9a88253dc..b7ec03e8f0a 100644 --- a/docs/sources/explore/_index.md +++ b/docs/sources/explore/_index.md @@ -25,6 +25,8 @@ If you just want to explore your data and do not want to create a dashboard, the - [Query management in Explore]({{< relref "query-management/" >}}) - [Logs integration in Explore]({{< relref "logs-integration/" >}}) - [Trace integration in Explore]({{< relref "trace-integration/" >}}) +- [Explore metrics]({{< relref "explore-metrics/" >}}) +- [Correlations Editor in Explore]({{< relref "correlations-editor-in-explore/" >}}) - [Inspector in Explore]({{< relref "explore-inspector/" >}}) ## Start exploring From 917cbce448e7e89d69b1b70c59cedaa8d6ffc956 Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:42:24 +0100 Subject: [PATCH 112/222] Scenes: Remove lodash usage from DetectChangesWorker (#86683) --- .betterer.results | 5 +- .../saving/DetectChangesWorker.ts | 16 ++- .../saving/getDashboardChanges.ts | 127 +++++++++--------- 3 files changed, 81 insertions(+), 67 deletions(-) diff --git a/.betterer.results b/.betterer.results index 12e65629126..ac9529357cb 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2413,8 +2413,9 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "public/app/features/dashboard-scene/saving/getDashboardChanges.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"], + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"] ], "public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], diff --git a/public/app/features/dashboard-scene/saving/DetectChangesWorker.ts b/public/app/features/dashboard-scene/saving/DetectChangesWorker.ts index 3dc6e7ec8b0..e8a3f2f6abe 100644 --- a/public/app/features/dashboard-scene/saving/DetectChangesWorker.ts +++ b/public/app/features/dashboard-scene/saving/DetectChangesWorker.ts @@ -1,10 +1,16 @@ -// Worker is not three shakable, so we should not import the whole loadash library -// eslint-disable-next-line lodash/import-scope -import debounce from 'lodash/debounce'; - import { getDashboardChanges } from './getDashboardChanges'; -self.onmessage = debounce((e: MessageEvent<{ initial: any; changed: any }>) => { +function _debounce(f: (...args: T[]) => void, timeout: number) { + let timeoutId: NodeJS.Timeout | undefined = undefined; + return (...theArgs: T[]) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + f(...theArgs); + }, timeout); + }; +} + +self.onmessage = _debounce((e: MessageEvent<{ initial: any; changed: any }>) => { const result = getDashboardChanges(e.data.initial, e.data.changed, false, false, false); self.postMessage(result); }, 500); diff --git a/public/app/features/dashboard-scene/saving/getDashboardChanges.ts b/public/app/features/dashboard-scene/saving/getDashboardChanges.ts index 9231ee43f5e..8a8b1426d68 100644 --- a/public/app/features/dashboard-scene/saving/getDashboardChanges.ts +++ b/public/app/features/dashboard-scene/saving/getDashboardChanges.ts @@ -1,10 +1,33 @@ -import { compare, Operation } from 'fast-json-patch'; +import { compare } from 'fast-json-patch'; // @ts-ignore import jsonMap from 'json-source-map'; -import { flow, get, isEqual, sortBy, tail } from 'lodash'; -import { AdHocVariableModel, TypedVariableModel } from '@grafana/data'; -import { Dashboard } from '@grafana/schema'; +import type { AdHocVariableModel, TypedVariableModel } from '@grafana/data'; +import type { Dashboard, VariableOption } from '@grafana/schema'; + +export function get(obj: any, keys: string[]) { + try { + let val = obj; + for (const key of keys) { + val = val[key]; + } + return val; + } catch (err) { + return undefined; + } +} + +export function deepEqual(a: string | string[], b: string | string[]) { + return ( + typeof a === typeof b && + ((typeof a === 'string' && a === b) || + (Array.isArray(a) && a.length === b.length && a.every((val, i) => val === b[i]))) + ); +} + +export function isEqual(a: VariableOption | undefined, b: VariableOption | undefined) { + return a === b || (a && b && a.selected === b.selected && deepEqual(a.text, b.text) && deepEqual(a.value, b.value)); +} export function getDashboardChanges( initial: Dashboard, @@ -28,11 +51,8 @@ export function getDashboardChanges( } const diff = jsonDiff(initialSaveModel, changedSaveModel); + const diffCount = Object.values(diff).reduce((acc, cur) => acc + cur.length, 0); - let diffCount = 0; - for (const d of Object.values(diff)) { - diffCount += d.length; - } return { changedSaveModel, initialSaveModel, @@ -63,7 +83,7 @@ export function applyVariableChanges(saveModel: Dashboard, originalSaveModel: Da } // Old schema property that never should be in persisted model - if (original.current && Object.hasOwn(original.current, 'selected')) { + if (original.current) { delete original.current.selected; } @@ -75,11 +95,9 @@ export function applyVariableChanges(saveModel: Dashboard, originalSaveModel: Da const typed = variable as TypedVariableModel; if (typed.type === 'adhoc') { typed.filters = (original as AdHocVariableModel).filters; - } else { - if (typed.type !== 'groupby') { - variable.current = original.current; - variable.options = original.options; - } + } else if (typed.type !== 'groupby') { + variable.current = original.current; + variable.options = original.options; } } } @@ -95,60 +113,49 @@ export type Diff = { startLineNumber: number; }; -export type Diffs = { - [key: string]: Diff[]; -}; +export type Diffs = Record; -export type JSONValue = string | Dashboard; - -export const jsonDiff = (lhs: JSONValue, rhs: JSONValue): Diffs => { +export const jsonDiff = (lhs: Dashboard, rhs: Dashboard): Diffs => { const diffs = compare(lhs, rhs); const lhsMap = jsonMap.stringify(lhs, null, 2); const rhsMap = jsonMap.stringify(rhs, null, 2); - const getDiffInformation = (diffs: Operation[]): Diff[] => { - return diffs.map((diff) => { - let originalValue = undefined; - let value = undefined; - let startLineNumber = 0; + const diffInfo = diffs.map((diff) => { + let originalValue = undefined; + let value = undefined; + let startLineNumber = 0; - const path = tail(diff.path.split('/')); + const path = diff.path.split('/').slice(1); - if (diff.op === 'replace' && rhsMap.pointers[diff.path]) { - originalValue = get(lhs, path); - value = diff.value; - startLineNumber = rhsMap.pointers[diff.path].value.line; - } - if (diff.op === 'add' && rhsMap.pointers[diff.path]) { - value = diff.value; - startLineNumber = rhsMap.pointers[diff.path].value.line; - } - if (diff.op === 'remove' && lhsMap.pointers[diff.path]) { - originalValue = get(lhs, path); - startLineNumber = lhsMap.pointers[diff.path].value.line; - } + if (diff.op === 'replace' && rhsMap.pointers[diff.path]) { + originalValue = get(lhs, path); + value = diff.value; + startLineNumber = rhsMap.pointers[diff.path].value.line; + } else if (diff.op === 'add' && rhsMap.pointers[diff.path]) { + value = diff.value; + startLineNumber = rhsMap.pointers[diff.path].value.line; + } else if (diff.op === 'remove' && lhsMap.pointers[diff.path]) { + originalValue = get(lhs, path); + startLineNumber = lhsMap.pointers[diff.path].value.line; + } - return { - op: diff.op, - value, - path, - originalValue, - startLineNumber, - }; - }); - }; + return { + op: diff.op, + value, + path, + originalValue, + startLineNumber, + }; + }); - const sortByLineNumber = (diffs: Diff[]) => sortBy(diffs, 'startLineNumber'); - const groupByPath = (diffs: Diff[]) => - diffs.reduce>((acc, value) => { - const groupKey: string = value.path[0]; - if (!acc[groupKey]) { - acc[groupKey] = []; - } - acc[groupKey].push(value); - return acc; - }, {}); + const sortedDiffs = diffInfo.sort((a, b) => a.startLineNumber - b.startLineNumber); + const grouped = sortedDiffs.reduce>((acc, value) => { + const groupKey = value.path[0]; + acc[groupKey] ??= []; + acc[groupKey].push(value); - // return 1; - return flow([getDiffInformation, sortByLineNumber, groupByPath])(diffs); + return acc; + }, {}); + + return grouped; }; From 28e86c3edbafd39b58fd2bac14ba7ff3afb1b96a Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Thu, 25 Apr 2024 17:44:55 +0300 Subject: [PATCH 113/222] Mention the migrator in the secrets service readme (#86922) mention the migrator in the secrets service readme --- pkg/services/secrets/secrets.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/services/secrets/secrets.go b/pkg/services/secrets/secrets.go index c54535a1d0a..282c1d42f3f 100644 --- a/pkg/services/secrets/secrets.go +++ b/pkg/services/secrets/secrets.go @@ -10,6 +10,10 @@ import ( // Service is an envelope encryption service in charge of encrypting/decrypting secrets. // It is a replacement for encryption.Service // +// For all encrypted secrets stored in the database, a migrator is needed to re-encrypt +// the secrets every time the encryption key has been rotated. Please add your database +// secrets to the migrator slice available in ./migrator/migrator.go. +// //go:generate mockery --name Service --structname MockService --outpkg fakes --filename mock_service.go --output ./fakes/ type Service interface { // Encrypt MUST NOT be used within database transactions, it may cause database locks. From 42778de2b4bcde8508ceefc69f3617c40bd72460 Mon Sep 17 00:00:00 2001 From: antonio <45235678+tonypowa@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:01:23 +0200 Subject: [PATCH 114/222] alerting>intro>templates (#86850) * alerting>intro>templates * restored alias url * numbering * diagram * Update docs/sources/alerting/fundamentals/notifications/message-templating.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * formatting * figure fix * Brenda edits * Brenda edit 2 * new diagram * new diagram2 --------- Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> --- .../notifications/message-templating.md | 63 +++++++------------ 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/docs/sources/alerting/fundamentals/notifications/message-templating.md b/docs/sources/alerting/fundamentals/notifications/message-templating.md index 9e6d33eae91..6f938729129 100644 --- a/docs/sources/alerting/fundamentals/notifications/message-templating.md +++ b/docs/sources/alerting/fundamentals/notifications/message-templating.md @@ -4,7 +4,7 @@ aliases: - ../../alert-rules/message-templating/ # /docs/grafana//alerting/alert-rules/message-templating/ - ../../unified-alerting/message-templating/ # /docs/grafana//alerting/unified-alerting/message-templating/ canonical: https://grafana.com/docs/grafana/latest/alerting/fundamentals/notifications/message-templating/ -description: Learn about notification templating +description: Learn about templates keywords: - grafana - alerting @@ -16,57 +16,36 @@ labels: - cloud - enterprise - oss -title: Notification templates +title: Templates weight: 114 --- -# Notification templates +## Templates -Notifications sent via contact points are built using notification templates. Grafana's default templates are based on the [Go templating system](https://golang.org/pkg/text/template) where some fields are evaluated as text, while others are evaluated as HTML (which can affect escaping). +Use templating to customize, format, and reuse alert notification messages. Create more flexible and informative alert notification messages by incorporating dynamic content, such as metric values, labels, and other contextual information. -The default template [default_template.go](https://github.com/grafana/alerting/blob/main/templates/default_template.go) is a useful reference for custom templates. +In Grafana, there are two ways to template your alert notification messages: -Since most of the contact point fields can be templated, you can create reusable custom templates and use them in multiple contact points. +1. Labels and annotations -### Using templates + - Template labels and annotations in alert rules. + - Labels and annotations contain information about an alert. + - Labels are used to differentiate an alert from all other alerts, while annotations are used to add additional information to an existing alert. -The following example shows how to use default templates to render an alert message in Slack. The message title contains a count of alerts that are firing or were resolved. The message body lists the alerts and their status. +2. Notification templates -{{< figure src="/static/img/docs/alerting/unified/contact-points-template-fields-8-0.png" class="docs-image--no-shadow" max-width= "550px" caption="Default template" >}} + - Template notifications in contact points. + - Add notification templates to contact points for reuse and consistent messaging in your notifications. + - Use notification templates to change the title, message, and format of the message in your notifications. -The following example shows the use of a custom template within one of the contact point fields. +This diagram illustrates the entire process of templating, from the creation of labels and annotations in alert rules or notification templates in contact points, to what they look like when exported and applied in your alert notification messages. -{{< figure src="/static/img/docs/alerting/unified/contact-points-use-template-8-0.png" class="docs-image--no-shadow" max-width= "550px" caption="Default template" >}} +{{< figure src="/media/docs/alerting/grafana-templating-diagram-2.jpg" max-width="1200px" caption="How Templating works" >}} -### Nested templates +In this diagram: -You can embed templates within other templates. - -For example, you can define a template fragment using the `define` keyword: - -``` -{{ define "mytemplate" }} - {{ len .Alerts.Firing }} firing. {{ len .Alerts.Resolved }} resolved. -{{ end }} -``` - -You can then embed custom templates within this fragment using the `template` keyword. For example: - -``` -Alert summary: -{{ template "mytemplate" . }} -``` - -You can use any of the following built-in template options to embed custom templates. - -| Name | Notes | -| ----------------------- | ------------------------------------------------------------ | -| `default.title` | Displays high-level status information. | -| `default.message` | Provides a formatted summary of firing and resolved alerts. | -| `teams.default.message` | Similar to `default.message`, formatted for Microsoft Teams. | - -### HTML in notification templates - -HTML in alerting notification templates is escaped. We do not support rendering of HTML in the resulting notification. - -Some notifiers support alternative methods of changing the look and feel of the resulting notification. For example, Grafana installs the base template for alerting emails to `/public/emails/ng_alert_notification.html`. You can edit this file to change the appearance of all alerting emails. +- **Monitored Application**: A web server, database, or any other service generating metrics. For example, it could be an NGINX server providing metrics about request rates, response times, and so on. +- **Prometheus**: Prometheus collects metrics from the monitored application. For example, it might scrape metrics from the NGINX server, including labels like instance (the server hostname) and job (the service name). +- **Grafana**: Grafana queries Prometheus to retrieve metrics data. For example, you might create an alert rule to monitor NGINX request rates over time, and template labels or annotations based on the instance label. +- **Alertmanager**: Part of the Prometheus ecosystem, Alertmanager handles alert notifications. For example, if the request rate exceeds a certain threshold on a particular NGINX server, Alertmanager can send an alert notification to, for example, Slack or email, including the server name and the exceeded threshold (the instance label will be interpolated, and the actual server name will appear in the alert notification). +- **Alert notification**: When an alert rule condition is met, Alertmanager sends a notification to various channels such as Slack, Grafana OnCall, etc. These notifications can include information from the labels associated with the alerting rule. For example, if an alert triggers due to high CPU usage on a specific server, the notification message can include details like server name (instance label), disk usage percentage, and the threshold that was exceeded. From ac152ca4160359709e489c9bd39a46d914776f2f Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 25 Apr 2024 17:25:48 +0200 Subject: [PATCH 115/222] Chore: Upgrade grpc-ecosystem/go-grpc-middleware to v2 (#86724) * Chore: Replace deprecated prometheus grpc middleware * go mod tidy without enterprise * with updated sdk branch * sdk v0.226.0 * remove deprecated opentracing support for outgoing plugin requests * migrate to github.com/grpc-ecosystem/go-grpc-middleware/v2 * fix --- go.mod | 19 ++-- go.sum | 32 +++--- go.work.sum | 47 +-------- pkg/apiserver/go.mod | 16 +-- pkg/apiserver/go.sum | 20 ++-- pkg/build/wire/go.mod | 1 + pkg/build/wire/go.sum | 2 +- pkg/components/loki/lokigrpc/client.go | 2 +- pkg/extensions/main.go | 3 +- .../backendplugin/grpcplugin/client.go | 7 -- pkg/promlib/go.mod | 14 +-- pkg/promlib/go.sum | 99 ++----------------- pkg/services/grpcserver/service.go | 27 +++-- pkg/services/store/entity/client_wrapper.go | 2 +- 14 files changed, 84 insertions(+), 207 deletions(-) diff --git a/go.mod b/go.mod index 93830c46345..c895906637c 100644 --- a/go.mod +++ b/go.mod @@ -51,8 +51,7 @@ require ( github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code github.com/grafana/grafana-aws-sdk v0.25.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.0.1 // @grafana/partner-datasources - github.com/grafana/grafana-plugin-sdk-go v0.224.0 // @grafana/plugins-platform-backend - github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // @grafana/grafana-backend-group + github.com/grafana/grafana-plugin-sdk-go v0.226.0 // @grafana/plugins-platform-backend github.com/hashicorp/go-hclog v1.6.3 // @grafana/plugins-platform-backend github.com/hashicorp/go-plugin v1.6.0 // @grafana/plugins-platform-backend github.com/hashicorp/go-version v1.6.0 // @grafana/grafana-backend-group @@ -75,8 +74,8 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/alertmanager v0.26.0 // @grafana/alerting-squad-backend github.com/prometheus/client_golang v1.19.0 // @grafana/alerting-squad-backend - github.com/prometheus/client_model v0.6.0 // @grafana/grafana-backend-group - github.com/prometheus/common v0.48.0 // @grafana/alerting-squad-backend + github.com/prometheus/client_model v0.6.1 // @grafana/grafana-backend-group + github.com/prometheus/common v0.53.0 // @grafana/alerting-squad-backend github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 // @grafana/alerting-squad-backend github.com/robfig/cron/v3 v3.0.1 // @grafana/grafana-backend-group github.com/russellhaering/goxmldsig v1.4.0 // @grafana/grafana-backend-group @@ -97,7 +96,7 @@ require ( golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // @grafana/alerting-squad-backend golang.org/x/net v0.24.0 // @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.19.0 // @grafana/identity-access-team - golang.org/x/sync v0.6.0 // @grafana/alerting-squad-backend + golang.org/x/sync v0.7.0 // @grafana/alerting-squad-backend golang.org/x/time v0.5.0 // @grafana/grafana-backend-group golang.org/x/tools v0.18.0 // @grafana/grafana-as-code gonum.org/v1/gonum v0.12.0 // @grafana/observability-metrics @@ -128,7 +127,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/deepmap/oapi-codegen v1.13.0 // @grafana/grafana-as-code @@ -184,7 +183,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.11.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/procfs v0.14.0 // indirect github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b // indirect github.com/rs/cors v1.10.1 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect @@ -423,7 +422,7 @@ require ( ) require ( - github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect + github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group @@ -471,6 +470,10 @@ require github.com/getkin/kin-openapi v0.120.0 // @grafana/grafana-as-code require github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 // @grafana/identity-access-team +require github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // @grafana/plugins-platform-backend + +require github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // @grafana/grafana-backend-group + require ( cloud.google.com/go/auth v0.2.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.1 // indirect diff --git a/go.sum b/go.sum index 0f90c95e311..602a0a5d2d2 100644 --- a/go.sum +++ b/go.sum @@ -1352,8 +1352,8 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/apache/arrow/go/arrow v0.0.0-20210223225224-5bea62493d91/go.mod h1:c9sxoIT3YgLxH4UhLOCKaBlEojuMhVYpk4Ntv3opUTQ= github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 h1:q4dksr6ICHXqG5hm0ZW5IHyeEJXoIJSOZeBLmWPNeIQ= github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= @@ -1527,8 +1527,9 @@ github.com/centrifugal/protocol v0.10.0/go.mod h1:Tq5I1mBpLHkLxNM9gfb3Gth+sTE2kK github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= @@ -2193,8 +2194,8 @@ github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkr github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ= github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= -github.com/grafana/grafana-plugin-sdk-go v0.224.0 h1:WBpKJEhzEcGBAmmNB/OGGF/pHrt654O3m7idjnpSc40= -github.com/grafana/grafana-plugin-sdk-go v0.224.0/go.mod h1:dR9hYmI18hOMArvMrQ1O7oiNOUqCGbxDAe2t6z1xLH8= +github.com/grafana/grafana-plugin-sdk-go v0.226.0 h1:PDnxWbQDn9GXfp62MH604GZ73j0fsyxyrDhpm08N5vY= +github.com/grafana/grafana-plugin-sdk-go v0.226.0/go.mod h1:j5TwvdShpKdgWgE4Tvk30c5bO9tKhO5wjZ1xwGhFBQg= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 h1:hpyusz8c3yRFoJPlA0o34rWnsLbaOOBZleqRhFBi5Lg= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4/go.mod h1:vrRQJuNprTWqwm6JPxHf3BoTJhvO15QMEjQ7Q/YUOnI= github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 h1:tIbI5zgos92vwJ8lV3zwHwuxkV03GR3FGLkFW9V5LxY= @@ -2223,6 +2224,10 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= @@ -2842,8 +2847,8 @@ github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3d github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= -github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -2860,8 +2865,9 @@ github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJ github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= +github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= @@ -2879,8 +2885,9 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= +github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= github.com/prometheus/prometheus v0.49.0 h1:i0CEhreJo3ZcZNeK7ulISinCac0MgL0krVOGgNmfFRY= github.com/prometheus/prometheus v0.49.0/go.mod h1:aDogiyqmv3aBIWDb5z5Sdcxuuf2BOfiJwOIm9JGpMnI= github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b h1:zd/2RNzIRkoGGMjE+YIsZ85CnDIz672JK2F3Zl4vux4= @@ -3228,7 +3235,6 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -3246,7 +3252,6 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= @@ -3546,8 +3551,9 @@ golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -3648,7 +3654,6 @@ golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -3788,7 +3793,6 @@ golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/go.work.sum b/go.work.sum index 84638d0ba95..322a750dfb5 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,3 +1,4 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1 h1:vp9EaPFSb75qe/793x58yE5fY1IJ/gdxb/kcDUzavtI= buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1/go.mod h1:YDq2B5X5BChU0lxAG5MxHpDb8mx1fv9OGtF2mwOe7hY= buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.28.1-20221127060915-a1ecdc58eccd.4 h1:z3Xc9n8yZ5k/Xr4ZTuff76TAYP20dWy7ZBV4cGIpbkM= @@ -172,7 +173,6 @@ cloud.google.com/go/gkemulticloud v1.1.1 h1:rsSZAGLhyjyE/bE2ToT5fqo1qSW7S+Ubsc9j cloud.google.com/go/gkemulticloud v1.1.1/go.mod h1:C+a4vcHlWeEIf45IB5FFR5XGjTeYhF83+AYIpTy4i2Q= cloud.google.com/go/grafeas v0.3.0 h1:oyTL/KjiUeBs9eYLw/40cpSZglUC+0F7X4iu/8t7NWs= cloud.google.com/go/grafeas v0.3.4 h1:D4x32R/cHX3MTofKwirz015uEdVk4uAxvZkZCZkOrF4= -cloud.google.com/go/grafeas v0.3.4/go.mod h1:A5m316hcG+AulafjAbPKXBO/+I5itU4LOdKO2R/uDIc= cloud.google.com/go/gsuiteaddons v1.6.4 h1:uuw2Xd37yHftViSI8J2hUcCS8S7SH3ZWH09sUDLW30Q= cloud.google.com/go/gsuiteaddons v1.6.5 h1:CZEbaBwmbYdhFw21Fwbo+C35HMe36fTE0FBSR4KSfWg= cloud.google.com/go/gsuiteaddons v1.6.5/go.mod h1:Lo4P2IvO8uZ9W+RaC6s1JVxo42vgy+TX5a6hfBZ0ubs= @@ -428,7 +428,6 @@ github.com/apache/arrow/go/v12 v12.0.1 h1:JsR2+hzYYjgSUkBSaahpqCetqZMr76djX80fF/ github.com/apache/arrow/go/v13 v13.0.0 h1:kELrvDQuKZo8csdWYqBQfyi431x6Zs/YJTEgUuSVcWk= github.com/apache/arrow/go/v13 v13.0.0/go.mod h1:W69eByFNO0ZR30q1/7Sr9d83zcVZmF2MiP3fFYAWJOc= github.com/apache/arrow/go/v14 v14.0.2 h1:N8OkaJEOfI3mEZt07BIkvo4sC6XDbL+48MBPWO5IONw= -github.com/apache/arrow/go/v14 v14.0.2/go.mod h1:u3fgh3EdgN/YQ8cVQRguVW3R+seMybFg8QBQ5LU+eBY= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= @@ -446,6 +445,7 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= +github.com/bufbuild/protovalidate-go v0.2.1/go.mod h1:e7XXDtlxj5vlEyAgsrxpzayp4cEMKCSSb8ZCkin+MVA= github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw= github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -611,20 +611,11 @@ github.com/grafana/grafana-plugin-sdk-go v0.212.0/go.mod h1:qsI4ktDf0lig74u8SLPJ github.com/grafana/grafana-plugin-sdk-go v0.215.0/go.mod h1:nBsh3jRItKQUXDF2BQkiQCPxqrsSQeb+7hiFyJTO1RE= github.com/grafana/grafana-plugin-sdk-go v0.216.0/go.mod h1:FdvSvOliqpVLnytM7e89zCFyYPDE6VOn9SIjVQRvVxM= github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b h1:HCbWyVL6vi7gxyO76gQksSPH203oBJ1MJ3JcG1OQlsg= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b/go.mod h1:01sXtHoRwI8W324IPAzuxDFOmALqYLCOhvSC2fUHWXc= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= -github.com/hamba/avro/v2 v2.17.2 h1:6PKpEWzJfNnvBgn7m2/8WYaDOUASxfDU+Jyb4ojDgFY= github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= -github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc= -github.com/hanwen/go-fuse/v2 v2.1.0 h1:+32ffteETaLYClUj0a3aHjZ1hOPxxaNEHiZiujuDaek= -github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= -github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= -github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= @@ -634,7 +625,6 @@ github.com/hudl/fargo v1.4.0 h1:ZDDILMbB37UlAVLlWcJ2Iz1XuahZZTDZfdCKeclfq2s= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91 h1:KyZDvZ/GGn+r+Y3DKZ7UOQ/TP4xV6HNkrwiVMB1GnNY= github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= -github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= github.com/influxdata/influxdb v1.7.6 h1:8mQ7A/V+3noMGCt/P9pD09ISaiz9XvgCk303UYA3gcs= @@ -672,16 +662,7 @@ github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o= github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= -github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d h1:c93kUJDtVAXFEhsCh5jSxyOJmFHuzcihnslQiX8Urwo= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0= -github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= -github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY= -github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= -github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs= github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= @@ -743,6 +724,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/jaegerexporter v0.74.0 h1:0dve/IbuHfQOnlIBQQwpCxIeMp7uig9DQVuvisWPDRs= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/jaegerexporter v0.74.0/go.mod h1:bIeSj+SaZdP3CE9Xae+zurdQC6DXX0tPP6NAEVmgtt4= @@ -831,29 +813,18 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= -github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e h1:mOtuXaRAbVZsxAHVdPR3IjfmN8T1h2iczJLynhLybf8= github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/substrait-io/substrait-go v0.4.2 h1:buDnjsb3qAqTaNbOR7VKmNgXf4lYQxWEcnSGUWBtmN8= github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg= -github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= @@ -906,7 +877,6 @@ github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= -go.einride.tech/aip v0.66.0/go.mod h1:qAhMsfT7plxBX+Oy7Huol6YUvZ0ZzdUz26yZsQwfl1M= go.opentelemetry.io/collector v0.74.0 h1:0s2DKWczGj/pLTsXGb1P+Je7dyuGx9Is4/Dri1+cS7g= go.opentelemetry.io/collector v0.74.0/go.mod h1:7NjZAvkhQ6E+NLN4EAH2hw3Nssi+F14t7mV7lMNXCto= go.opentelemetry.io/collector/component v0.74.0 h1:W32ILPgbA5LO+m9Se61hbbtiLM6FYusNM36K5/CCOi0= @@ -954,15 +924,14 @@ go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808 h1:+Kc94D8UVEVxJnLXp/+FMfqQARZtWHfVrcRtcG8aT3g= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= @@ -975,14 +944,12 @@ google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxm google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:PVreiBMirk8ypES6aw9d4p6iiBNSIfZEBqr3UGoAi2E= google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231120223509-83a465c0220f h1:hL+1ptbhFoeL1HcROQ8OGXaqH0jYRRibgWQWco0/Ugc= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0 h1:Y6QQt9D/syZt/Qgnz5a1y2O3WunQeeVDfS9+Xr82iFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe h1:weYsP+dNijSQVoLAb5bpUos3ciBpNU/NEVlHFKrk8pg= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa h1:wBkzraZsSqhj1M4L/nMrljUU6XasJkgHvUsq8oRGwF0= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:IN9OQUXZ0xT+26MDwZL8fJcYw+y99b0eYPA2U15Jt8o= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= @@ -1005,18 +972,14 @@ gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= -gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6 h1:WN8Lymy+dCTDHgn4vhUSNIB6U+0sDiv/c9Zdr0UeAnI= k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6/go.mod h1:l0ukbPS0lwFxOzSq5ZqjutzF+5IL2TLp495PswRPSZk= -k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo= k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8= k8s.io/kms v0.29.0/go.mod h1:mB0f9HLxRXeXUfHfn1A7rpwOlzXI1gIWu86z6buNoYA= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kube-openapi v0.0.0-20231214164306-ab13479f8bf8/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index c7ebc754d46..53b3eaa2ec0 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -5,7 +5,7 @@ go 1.21.0 require ( github.com/bwmarrin/snowflake v0.3.0 github.com/gorilla/mux v1.8.1 - github.com/grafana/grafana-plugin-sdk-go v0.224.0 + github.com/grafana/grafana-plugin-sdk-go v0.226.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f github.com/stretchr/testify v1.9.0 golang.org/x/mod v0.15.0 @@ -21,13 +21,13 @@ require ( require ( github.com/BurntSushi/toml v1.3.2 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect + github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect github.com/apache/arrow/go/v15 v15.0.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 // indirect github.com/coreos/go-semver v0.3.1 // indirect @@ -60,6 +60,8 @@ require ( github.com/google/pprof v0.0.0-20231205033806-a5a03c77bf08 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect @@ -91,9 +93,9 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.19.0 // indirect - github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.53.0 // indirect + github.com/prometheus/procfs v0.14.0 // indirect github.com/rivo/uniseg v0.3.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -129,7 +131,7 @@ require ( golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/oauth2 v0.19.0 // indirect - golang.org/x/sync v0.6.0 // indirect + golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/term v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 78e4ed48566..5a8bcf7c0e1 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -4,8 +4,7 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8 github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= +github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE= github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -23,8 +22,7 @@ github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/ github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 h1:aPflPkRFkVwbW6dmcVqfgwp1i+UWGFH6VgR1Jim5Ygc= @@ -129,11 +127,13 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/grafana-plugin-sdk-go v0.224.0 h1:WBpKJEhzEcGBAmmNB/OGGF/pHrt654O3m7idjnpSc40= +github.com/grafana/grafana-plugin-sdk-go v0.226.0 h1:PDnxWbQDn9GXfp62MH604GZ73j0fsyxyrDhpm08N5vY= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f h1:+CK3tH3XrAAqx5urmVqpgSxMrL2MlpTOnLVSU4w4IjY= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f/go.mod h1:ZxIaCOlDmFupiL55aLU+Qp7O1dgwkDMBAQBK7wnEVBg= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= @@ -233,12 +233,11 @@ github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7km github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -384,8 +383,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/pkg/build/wire/go.mod b/pkg/build/wire/go.mod index 694a3b60da9..86688b89207 100644 --- a/pkg/build/wire/go.mod +++ b/pkg/build/wire/go.mod @@ -6,5 +6,6 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/subcommands v1.2.0 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 + golang.org/x/sync v0.7.0 // indirect golang.org/x/tools v0.18.0 ) diff --git a/pkg/build/wire/go.sum b/pkg/build/wire/go.sum index 33ae69585d9..5d5c60f18f5 100644 --- a/pkg/build/wire/go.sum +++ b/pkg/build/wire/go.sum @@ -22,8 +22,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/components/loki/lokigrpc/client.go b/pkg/components/loki/lokigrpc/client.go index e8a0c11808c..921d14b2af5 100644 --- a/pkg/components/loki/lokigrpc/client.go +++ b/pkg/components/loki/lokigrpc/client.go @@ -5,7 +5,7 @@ import ( "crypto/tls" "errors" - grpcretry "github.com/grpc-ecosystem/go-grpc-middleware/retry" + grpcretry "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go index 2a8e7d3005d..5394eb48e94 100644 --- a/pkg/extensions/main.go +++ b/pkg/extensions/main.go @@ -18,7 +18,8 @@ import ( _ "github.com/grafana/dskit/backoff" _ "github.com/grafana/dskit/flagext" _ "github.com/grafana/gofpdf" - _ "github.com/grpc-ecosystem/go-grpc-middleware" + _ "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" + _ "github.com/grpc-ecosystem/go-grpc-middleware/v2" _ "github.com/hashicorp/go-multierror" _ "github.com/hashicorp/golang-lru/v2" _ "github.com/linkedin/goavro/v2" diff --git a/pkg/plugins/backendplugin/grpcplugin/client.go b/pkg/plugins/backendplugin/grpcplugin/client.go index eacd1611e2b..5d352314e75 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client.go +++ b/pkg/plugins/backendplugin/grpcplugin/client.go @@ -4,7 +4,6 @@ import ( "os/exec" "github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin" - grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" goplugin "github.com/hashicorp/go-plugin" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" @@ -54,12 +53,6 @@ func newClientConfig(executablePath string, args []string, env []string, skipHos Logger: logWrapper{Logger: logger}, AllowedProtocols: []goplugin.Protocol{goplugin.ProtocolGRPC}, GRPCDialOptions: []grpc.DialOption{ - grpc.WithChainUnaryInterceptor( - grpc_opentracing.UnaryClientInterceptor(), - ), - grpc.WithChainStreamInterceptor( - grpc_opentracing.StreamClientInterceptor(), - ), grpc.WithStatsHandler(otelgrpc.NewClientHandler()), }, } diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index c8ebd736f09..9f89fcbf839 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -3,11 +3,11 @@ module github.com/grafana/grafana/pkg/promlib go 1.21.0 require ( - github.com/grafana/grafana-plugin-sdk-go v0.224.0 + github.com/grafana/grafana-plugin-sdk-go v0.226.0 github.com/json-iterator/go v1.1.12 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.19.0 - github.com/prometheus/common v0.48.0 + github.com/prometheus/common v0.53.0 github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.24.0 @@ -25,7 +25,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect @@ -53,8 +53,8 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect @@ -80,8 +80,8 @@ require ( github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/procfs v0.14.0 // indirect github.com/rivo/uniseg v0.3.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index a820ca92494..30f4d0b2611 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -1,4 +1,3 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -11,8 +10,6 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/aws/aws-sdk-go v1.50.29 h1:Ol2FYzesF2tsQrgVSnDWRFI60+FsSqKKdt7MLlZKubc= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= @@ -21,15 +18,11 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 h1:aPflPkRFkVwbW6dmcVqfgwp1i+UWGFH6VgR1Jim5Ygc= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= @@ -47,10 +40,6 @@ github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac h1:9yrT5tmn9Zc github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= @@ -61,10 +50,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -78,18 +65,12 @@ github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdX github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -97,7 +78,6 @@ github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8i github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -110,13 +90,11 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/grafana-plugin-sdk-go v0.224.0 h1:WBpKJEhzEcGBAmmNB/OGGF/pHrt654O3m7idjnpSc40= +github.com/grafana/grafana-plugin-sdk-go v0.226.0 h1:PDnxWbQDn9GXfp62MH604GZ73j0fsyxyrDhpm08N5vY= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= @@ -148,12 +126,8 @@ github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= @@ -174,7 +148,6 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -194,32 +167,23 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 h1:etRZv4bJf9YAuyPWbyFufjkijfeoPSmyA5xNcd4DoyI= github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3/go.mod h1:plwr4+63Q1xL8oIdBDeU854um7Cct0Av8dhP44lutMw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -232,7 +196,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -240,13 +203,10 @@ github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:X github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -296,57 +256,37 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -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.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -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= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -356,14 +296,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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= @@ -376,40 +310,23 @@ golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSm golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= -google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/pkg/services/grpcserver/service.go b/pkg/services/grpcserver/service.go index 0eaf246e04d..4826ae948da 100644 --- a/pkg/services/grpcserver/service.go +++ b/pkg/services/grpcserver/service.go @@ -8,8 +8,7 @@ import ( "github.com/grafana/dskit/instrument" "github.com/grafana/dskit/middleware" "github.com/grafana/grafana-plugin-sdk-go/backend" - grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" - grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/auth" + grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" "github.com/prometheus/client_golang/prometheus" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -67,22 +66,18 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, authe // Default auth is admin token check, but this can be overridden by // services which implement ServiceAuthFuncOverride interface. - // See https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/auth/auth.go#L30. + // See https://github.com/grpc-ecosystem/go-grpc-middleware/blob/main/interceptors/auth/auth.go#L30. opts = append(opts, []grpc.ServerOption{ - grpc.UnaryInterceptor( - grpc_middleware.ChainUnaryServer( - grpcAuth.UnaryServerInterceptor(authenticator.Authenticate), - interceptors.TracingUnaryInterceptor(tracer), - interceptors.LoggingUnaryInterceptor(s.cfg, s.logger), // needs to be registered after tracing interceptor to get trace id - middleware.UnaryServerInstrumentInterceptor(grpcRequestDuration), - ), + grpc.ChainUnaryInterceptor( + grpcAuth.UnaryServerInterceptor(authenticator.Authenticate), + interceptors.TracingUnaryInterceptor(tracer), + interceptors.LoggingUnaryInterceptor(s.cfg, s.logger), // needs to be registered after tracing interceptor to get trace id + middleware.UnaryServerInstrumentInterceptor(grpcRequestDuration), ), - grpc.StreamInterceptor( - grpc_middleware.ChainStreamServer( - interceptors.TracingStreamInterceptor(tracer), - grpcAuth.StreamServerInterceptor(authenticator.Authenticate), - middleware.StreamServerInstrumentInterceptor(grpcRequestDuration), - ), + grpc.ChainStreamInterceptor( + interceptors.TracingStreamInterceptor(tracer), + grpcAuth.StreamServerInterceptor(authenticator.Authenticate), + middleware.StreamServerInstrumentInterceptor(grpcRequestDuration), ), }...) diff --git a/pkg/services/store/entity/client_wrapper.go b/pkg/services/store/entity/client_wrapper.go index 18d151e9851..a4b7572d7a2 100644 --- a/pkg/services/store/entity/client_wrapper.go +++ b/pkg/services/store/entity/client_wrapper.go @@ -3,7 +3,7 @@ package entity import ( "github.com/fullstorydev/grpchan" "github.com/fullstorydev/grpchan/inprocgrpc" - grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/auth" + grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" "google.golang.org/grpc" grpcUtils "github.com/grafana/grafana/pkg/services/store/entity/grpc" From 4bf9405ce49323fb2bd36ec8c65f950468081812 Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Thu, 25 Apr 2024 18:30:23 +0300 Subject: [PATCH 116/222] SSO: add SSO settings to secrets migrator (#86913) * add sso settings to secrets migrator * unify SSO settings in all log lines --- pkg/services/secrets/migrator/migrator.go | 3 + pkg/services/secrets/migrator/reencrypt.go | 89 +++++++++++++++++++ pkg/services/secrets/migrator/rollback.go | 70 +++++++++++++++ .../ssosettings/ssosettingsimpl/service.go | 11 +-- 4 files changed, 168 insertions(+), 5 deletions(-) diff --git a/pkg/services/secrets/migrator/migrator.go b/pkg/services/secrets/migrator/migrator.go index 3361bc097f8..02872935b9f 100644 --- a/pkg/services/secrets/migrator/migrator.go +++ b/pkg/services/secrets/migrator/migrator.go @@ -45,6 +45,7 @@ func ProvideSecretsMigrator( jsonSecret{tableName: "plugin_setting"}, b64Secret{simpleSecret: simpleSecret{tableName: "signing_key", columnName: "private_key"}, encoding: base64.StdEncoding}, alertingSecret{}, + ssoSettingsSecret{}, } return &SecretsMigrator{ @@ -157,6 +158,8 @@ type jsonSecret struct { type alertingSecret struct{} +type ssoSettingsSecret struct{} + func nowInUTC() string { return time.Now().UTC().Format("2006-01-02 15:04:05") } diff --git a/pkg/services/secrets/migrator/reencrypt.go b/pkg/services/secrets/migrator/reencrypt.go index 990774e6b86..6c1271277a4 100644 --- a/pkg/services/secrets/migrator/reencrypt.go +++ b/pkg/services/secrets/migrator/reencrypt.go @@ -11,6 +11,8 @@ import ( "github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/ssosettings/models" + "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingsimpl" ) func (s simpleSecret) ReEncrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore db.DB) bool { @@ -289,3 +291,90 @@ func (s alertingSecret) ReEncrypt(ctx context.Context, secretsSrv *manager.Secre return !anyFailure } + +func (s ssoSettingsSecret) ReEncrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore db.DB) bool { + results := make([]*models.SSOSettings, 0) + + err := sqlStore.WithDbSession(ctx, func(sess *db.Session) error { + return sess.Find(&results) + }) + + if err != nil { + logger.Warn("Failed to fetch SSO settings to re-encrypt", "err", err) + return false + } + + var anyFailure bool + + for _, result := range results { + err := sqlStore.InTransaction(ctx, func(ctx context.Context) error { + for field, value := range result.Settings { + if ssosettingsimpl.IsSecretField(field) { + decrypted, err := s.decryptValue(ctx, value, secretsSrv) + if err != nil { + logger.Warn("Could not decrypt SSO settings secret", "id", result.ID, "field", field, "error", err) + return err + } + + if decrypted == nil { + continue + } + + reencrypted, err := secretsSrv.Encrypt(ctx, decrypted, secrets.WithoutScope()) + if err != nil { + logger.Warn("Could not re-encrypt SSO settings secret", "id", result.ID, "field", field, "error", err) + return err + } + + result.Settings[field] = base64.RawStdEncoding.EncodeToString(reencrypted) + } + } + + err = sqlStore.WithDbSession(ctx, func(sess *db.Session) error { + _, err := sess.Where("id = ?", result.ID).Update(result) + return err + }) + if err != nil { + logger.Warn("Could not update SSO settings secrets while re-encrypting it", "id", result.ID, "error", err) + return err + } + + return nil + }) + + if err != nil { + anyFailure = true + } + } + + if anyFailure { + logger.Warn("SSO settings secrets have been re-encrypted with errors") + } else { + logger.Info("SSO settings secrets have been re-encrypted successfully") + } + + return !anyFailure +} + +func (s ssoSettingsSecret) decryptValue(ctx context.Context, value any, secretsSrv *manager.SecretsService) ([]byte, error) { + strValue, ok := value.(string) + if !ok { + return nil, fmt.Errorf("SSO secret value is not a string") + } + + if strValue == "" { + return nil, nil + } + + decoded, err := base64.RawStdEncoding.DecodeString(strValue) + if err != nil { + return nil, fmt.Errorf("could not decode base64-encoded SSO settings secret: %w", err) + } + + decrypted, err := secretsSrv.Decrypt(ctx, decoded) + if err != nil { + return nil, fmt.Errorf("could not decrypt SSO settings secret: %w", err) + } + + return decrypted, nil +} diff --git a/pkg/services/secrets/migrator/rollback.go b/pkg/services/secrets/migrator/rollback.go index c68c0962771..8a8838c0f96 100644 --- a/pkg/services/secrets/migrator/rollback.go +++ b/pkg/services/secrets/migrator/rollback.go @@ -10,6 +10,8 @@ import ( "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/secrets/manager" + "github.com/grafana/grafana/pkg/services/ssosettings/models" + "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingsimpl" ) func (s simpleSecret) Rollback( @@ -294,3 +296,71 @@ func (s alertingSecret) Rollback( return anyFailure } + +func (s ssoSettingsSecret) Rollback( + ctx context.Context, + secretsSrv *manager.SecretsService, + encryptionSrv encryption.Internal, + sqlStore db.DB, + secretKey string, +) (anyFailure bool) { + results := make([]*models.SSOSettings, 0) + + err := sqlStore.WithDbSession(ctx, func(sess *db.Session) error { + return sess.Find(&results) + }) + + if err != nil { + logger.Warn("Failed to fetch SSO settings to roll back") + return true + } + + for _, result := range results { + err := sqlStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + for field, value := range result.Settings { + if ssosettingsimpl.IsSecretField(field) { + decrypted, err := s.decryptValue(ctx, value, secretsSrv) + if err != nil { + logger.Warn("Could not decrypt SSO settings secret", "id", result.ID, "field", field, "error", err) + return err + } + + if decrypted == nil { + continue + } + + reencrypted, err := encryptionSrv.Encrypt(ctx, decrypted, secretKey) + if err != nil { + logger.Warn("Could not re-encrypt SSO settings secret", "id", result.ID, "field", field, "error", err) + return err + } + + result.Settings[field] = base64.RawStdEncoding.EncodeToString(reencrypted) + } + } + + err = sqlStore.WithDbSession(ctx, func(sess *db.Session) error { + _, err := sess.Where("id = ?", result.ID).Update(result) + return err + }) + if err != nil { + logger.Warn("Could not update SSO settings secrets while re-encrypting it", "id", result.ID, "error", err) + return err + } + + return nil + }) + + if err != nil { + anyFailure = true + } + } + + if anyFailure { + logger.Warn("SSO settings secrets have been rolled back with errors") + } else { + logger.Info("SSO settings secrets have been rolled back successfully") + } + + return anyFailure +} diff --git a/pkg/services/ssosettings/ssosettingsimpl/service.go b/pkg/services/ssosettings/ssosettingsimpl/service.go index b3edf2910a0..f1f2f3e97bc 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service.go @@ -324,7 +324,7 @@ func (s *Service) getFallbackStrategyFor(provider string) (ssosettings.FallbackS func (s *Service) encryptSecrets(ctx context.Context, settings map[string]any) (map[string]any, error) { result := make(map[string]any) for k, v := range settings { - if isSecret(k) && v != "" { + if IsSecretField(k) && v != "" { strValue, ok := v.(string) if !ok { return result, fmt.Errorf("failed to encrypt %s setting because it is not a string: %v", k, v) @@ -414,7 +414,7 @@ func (s *Service) mergeSSOSettings(dbSettings, systemSettings *models.SSOSetting func (s *Service) decryptSecrets(ctx context.Context, settings map[string]any) (map[string]any, error) { for k, v := range settings { - if isSecret(k) && v != "" { + if IsSecretField(k) && v != "" { strValue, ok := v.(string) if !ok { s.logger.Error("Failed to parse secret value, it is not a string", "key", k) @@ -449,7 +449,7 @@ func (s *Service) isProviderConfigurable(provider string) bool { func removeSecrets(settings map[string]any) map[string]any { result := make(map[string]any) for k, v := range settings { - if isSecret(k) { + if IsSecretField(k) { result[k] = setting.RedactedPassword continue } @@ -487,7 +487,7 @@ func mergeSettings(storedSettings, systemSettings map[string]any) map[string]any func mergeSecrets(settings map[string]any, storedSettings map[string]any) (map[string]any, error) { settingsWithSecrets := map[string]any{} for k, v := range settings { - if isSecret(k) { + if IsSecretField(k) { strValue, ok := v.(string) if !ok { return nil, fmt.Errorf("secret value is not a string") @@ -515,7 +515,8 @@ func overrideMaps(maps ...map[string]any) map[string]any { return result } -func isSecret(fieldName string) bool { +// IsSecretField returns true if the SSO settings field provided is a secret +func IsSecretField(fieldName string) bool { secretFieldPatterns := []string{"secret", "private", "certificate"} for _, v := range secretFieldPatterns { From 748b3c855c3963ab6ffd824a4ca7fe4771ef88f7 Mon Sep 17 00:00:00 2001 From: Ieva Date: Thu, 25 Apr 2024 16:46:24 +0100 Subject: [PATCH 117/222] Chore: Clean up team membership code (#86914) remove unused code, clean up commands --- pkg/services/team/model.go | 8 ++---- pkg/services/team/team.go | 5 ---- pkg/services/team/teamapi/team_members.go | 7 +++--- pkg/services/team/teamimpl/store.go | 30 ----------------------- pkg/services/team/teamimpl/team.go | 13 ---------- pkg/services/team/teamtest/team.go | 13 ---------- 6 files changed, 5 insertions(+), 71 deletions(-) diff --git a/pkg/services/team/model.go b/pkg/services/team/model.go index a4d4fe0a6a6..0426368de17 100644 --- a/pkg/services/team/model.go +++ b/pkg/services/team/model.go @@ -21,6 +21,8 @@ var ( ErrTeamMemberAlreadyAdded = errors.New("user is already added to this team") ) +const MemberPermissionName = "Member" + // Team model type Team struct { ID int64 `json:"id" xorm:"pk autoincr 'id'"` @@ -124,16 +126,10 @@ type TeamMember struct { type AddTeamMemberCommand struct { UserID int64 `json:"userId" binding:"Required"` - OrgID int64 `json:"-"` - TeamID int64 `json:"-"` - External bool `json:"-"` Permission dashboardaccess.PermissionType `json:"-"` } type UpdateTeamMemberCommand struct { - UserID int64 `json:"-"` - OrgID int64 `json:"-"` - TeamID int64 `json:"-"` Permission dashboardaccess.PermissionType `json:"permission"` } diff --git a/pkg/services/team/team.go b/pkg/services/team/team.go index 5f1bca40244..662f2136ae4 100644 --- a/pkg/services/team/team.go +++ b/pkg/services/team/team.go @@ -2,8 +2,6 @@ package team import ( "context" - - "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" ) type Service interface { @@ -14,10 +12,7 @@ type Service interface { GetTeamByID(ctx context.Context, query *GetTeamByIDQuery) (*TeamDTO, error) GetTeamsByUser(ctx context.Context, query *GetTeamsByUserQuery) ([]*TeamDTO, error) GetTeamIDsByUser(ctx context.Context, query *GetTeamIDsByUserQuery) ([]int64, error) - AddTeamMember(ctx context.Context, userID, orgID, teamID int64, isExternal bool, permission dashboardaccess.PermissionType) error - UpdateTeamMember(ctx context.Context, cmd *UpdateTeamMemberCommand) error IsTeamMember(orgId int64, teamId int64, userId int64) (bool, error) - RemoveTeamMember(ctx context.Context, cmd *RemoveTeamMemberCommand) error RemoveUsersMemberships(tx context.Context, userID int64) error GetUserTeamMemberships(ctx context.Context, orgID, userID int64, external bool) ([]*TeamMemberDTO, error) GetTeamMembers(ctx context.Context, query *GetTeamMembersQuery) ([]*TeamMemberDTO, error) diff --git a/pkg/services/team/teamapi/team_members.go b/pkg/services/team/teamapi/team_members.go index 89419b8ff09..e5b0b79e9e5 100644 --- a/pkg/services/team/teamapi/team_members.go +++ b/pkg/services/team/teamapi/team_members.go @@ -77,13 +77,12 @@ func (tapi *TeamAPI) addTeamMember(c *contextmodel.ReqContext) response.Response if err := web.Bind(c.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } - cmd.OrgID = c.SignedInUser.GetOrgID() - cmd.TeamID, err = strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64) + teamID, err := strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64) if err != nil { return response.Error(http.StatusBadRequest, "teamId is invalid", err) } - isTeamMember, err := tapi.teamService.IsTeamMember(c.SignedInUser.GetOrgID(), cmd.TeamID, cmd.UserID) + isTeamMember, err := tapi.teamService.IsTeamMember(c.SignedInUser.GetOrgID(), teamID, cmd.UserID) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to add team member.", err) } @@ -91,7 +90,7 @@ func (tapi *TeamAPI) addTeamMember(c *contextmodel.ReqContext) response.Response return response.Error(http.StatusBadRequest, "User is already added to this team", nil) } - err = addOrUpdateTeamMember(c.Req.Context(), tapi.teamPermissionsService, cmd.UserID, cmd.OrgID, cmd.TeamID, getPermissionName(cmd.Permission)) + err = addOrUpdateTeamMember(c.Req.Context(), tapi.teamPermissionsService, cmd.UserID, c.SignedInUser.GetOrgID(), teamID, team.MemberPermissionName) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to add Member to Team", err) } diff --git a/pkg/services/team/teamimpl/store.go b/pkg/services/team/teamimpl/store.go index f8a85618d99..2076c5cf95d 100644 --- a/pkg/services/team/teamimpl/store.go +++ b/pkg/services/team/teamimpl/store.go @@ -26,10 +26,7 @@ type store interface { GetByUser(ctx context.Context, query *team.GetTeamsByUserQuery) ([]*team.TeamDTO, error) GetIDsByUser(ctx context.Context, query *team.GetTeamIDsByUserQuery) ([]int64, error) RemoveUsersMemberships(ctx context.Context, userID int64) error - AddMember(ctx context.Context, userID, orgID, teamID int64, isExternal bool, permission dashboardaccess.PermissionType) error - UpdateMember(ctx context.Context, cmd *team.UpdateTeamMemberCommand) error IsMember(orgId int64, teamId int64, userId int64) (bool, error) - RemoveMember(ctx context.Context, cmd *team.RemoveTeamMemberCommand) error GetMemberships(ctx context.Context, orgID, userID int64, external bool) ([]*team.TeamMemberDTO, error) GetMembers(ctx context.Context, query *team.GetTeamMembersQuery) ([]*team.TeamMemberDTO, error) RegisterDelete(query string) @@ -350,19 +347,6 @@ WHERE tm.user_id=? AND tm.org_id=?;`, query.UserID, query.OrgID).Find(&queryResu return queryResult, nil } -// AddTeamMember adds a user to a team -func (ss *xormStore) AddMember(ctx context.Context, userID, orgID, teamID int64, isExternal bool, permission dashboardaccess.PermissionType) error { - return ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - if isMember, err := isTeamMember(sess, orgID, teamID, userID); err != nil { - return err - } else if isMember { - return team.ErrTeamMemberAlreadyAdded - } - - return addTeamMember(sess, orgID, teamID, userID, isExternal, permission) - }) -} - func getTeamMember(sess *db.Session, orgId int64, teamId int64, userId int64) (team.TeamMember, error) { rawSQL := `SELECT * FROM team_member WHERE org_id=? and team_id=? and user_id=?` var member team.TeamMember @@ -378,13 +362,6 @@ func getTeamMember(sess *db.Session, orgId int64, teamId int64, userId int64) (t return member, nil } -// UpdateTeamMember updates a team member -func (ss *xormStore) UpdateMember(ctx context.Context, cmd *team.UpdateTeamMemberCommand) error { - return ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - return updateTeamMember(sess, cmd.OrgID, cmd.TeamID, cmd.UserID, cmd.Permission) - }) -} - func (ss *xormStore) IsMember(orgId int64, teamId int64, userId int64) (bool, error) { var isMember bool @@ -458,13 +435,6 @@ func updateTeamMember(sess *db.Session, orgID, teamID, userID int64, permission return err } -// RemoveTeamMember removes a member from a team -func (ss *xormStore) RemoveMember(ctx context.Context, cmd *team.RemoveTeamMemberCommand) error { - return ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - return removeTeamMember(sess, cmd) - }) -} - // RemoveTeamMemberHook is called from team resource permission service // it removes a member from a team within the given transaction session func RemoveTeamMemberHook(sess *db.Session, cmd *team.RemoveTeamMemberCommand) error { diff --git a/pkg/services/team/teamimpl/team.go b/pkg/services/team/teamimpl/team.go index 11cef2ab58e..18e29595213 100644 --- a/pkg/services/team/teamimpl/team.go +++ b/pkg/services/team/teamimpl/team.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/setting" ) @@ -50,22 +49,10 @@ func (s *Service) GetTeamIDsByUser(ctx context.Context, query *team.GetTeamIDsBy return s.store.GetIDsByUser(ctx, query) } -func (s *Service) AddTeamMember(ctx context.Context, userID, orgID, teamID int64, isExternal bool, permission dashboardaccess.PermissionType) error { - return s.store.AddMember(ctx, userID, orgID, teamID, isExternal, permission) -} - -func (s *Service) UpdateTeamMember(ctx context.Context, cmd *team.UpdateTeamMemberCommand) error { - return s.store.UpdateMember(ctx, cmd) -} - func (s *Service) IsTeamMember(orgId int64, teamId int64, userId int64) (bool, error) { return s.store.IsMember(orgId, teamId, userId) } -func (s *Service) RemoveTeamMember(ctx context.Context, cmd *team.RemoveTeamMemberCommand) error { - return s.store.RemoveMember(ctx, cmd) -} - func (s *Service) RemoveUsersMemberships(ctx context.Context, userID int64) error { return s.store.RemoveUsersMemberships(ctx, userID) } diff --git a/pkg/services/team/teamtest/team.go b/pkg/services/team/teamtest/team.go index 2c6fccdf333..58e9fda9750 100644 --- a/pkg/services/team/teamtest/team.go +++ b/pkg/services/team/teamtest/team.go @@ -3,7 +3,6 @@ package teamtest import ( "context" - "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" "github.com/grafana/grafana/pkg/services/team" ) @@ -45,22 +44,10 @@ func (s *FakeService) GetTeamsByUser(ctx context.Context, query *team.GetTeamsBy return s.ExpectedTeamsByUser, s.ExpectedError } -func (s *FakeService) AddTeamMember(ctx context.Context, userID, orgID, teamID int64, isExternal bool, permission dashboardaccess.PermissionType) error { - return s.ExpectedError -} - -func (s *FakeService) UpdateTeamMember(ctx context.Context, cmd *team.UpdateTeamMemberCommand) error { - return s.ExpectedError -} - func (s *FakeService) IsTeamMember(orgId int64, teamId int64, userId int64) (bool, error) { return s.ExpectedIsMember, s.ExpectedError } -func (s *FakeService) RemoveTeamMember(ctx context.Context, cmd *team.RemoveTeamMemberCommand) error { - return s.ExpectedError -} - func (s *FakeService) RemoveUsersMemberships(ctx context.Context, userID int64) error { return s.ExpectedError } From e394e16073dd73fef589d2b42afbb9e0a9eed606 Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Thu, 25 Apr 2024 17:31:17 +0100 Subject: [PATCH 118/222] Auth: Force lowercase login/email for users (#86359) * [WIP]: Force lowercase login/email for user CRUD * warn and remove use of userCaseInsensitiveLogin check * remove log warning * reimplementation of the caseinsensitive * need to decide if we want the conflict check or not * remvoved the tests for conflict user by getEmail, getLogin * added tests for user lowercase migration * wip: emails next * tests for email lowercasing * review comments * optimized login and email lookup before migrating --- pkg/services/sqlstore/migrations/user_mig.go | 7 +- ...ice_account_multiple_org_login_migrator.go | 2 +- .../test/service_account_test.go | 2 +- .../user_lowercase_login_and_email_test.go | 253 ++++++++++++++++++ .../{user => usermig}/test/user_test.go | 0 .../usermig/user_lowercase_login_and_email.go | 97 +++++++ pkg/services/user/userimpl/store.go | 45 ++-- pkg/services/user/userimpl/store_test.go | 31 --- 8 files changed, 374 insertions(+), 63 deletions(-) rename pkg/services/sqlstore/migrations/{user => usermig}/service_account_multiple_org_login_migrator.go (99%) rename pkg/services/sqlstore/migrations/{user => usermig}/test/service_account_test.go (98%) create mode 100644 pkg/services/sqlstore/migrations/usermig/test/user_lowercase_login_and_email_test.go rename pkg/services/sqlstore/migrations/{user => usermig}/test/user_test.go (100%) create mode 100644 pkg/services/sqlstore/migrations/usermig/user_lowercase_login_and_email.go diff --git a/pkg/services/sqlstore/migrations/user_mig.go b/pkg/services/sqlstore/migrations/user_mig.go index 77ed12e5876..8603314a3d1 100644 --- a/pkg/services/sqlstore/migrations/user_mig.go +++ b/pkg/services/sqlstore/migrations/user_mig.go @@ -5,7 +5,7 @@ import ( "xorm.io/xorm" - "github.com/grafana/grafana/pkg/services/sqlstore/migrations/user" + "github.com/grafana/grafana/pkg/services/sqlstore/migrations/usermig" . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/util" ) @@ -157,7 +157,10 @@ func addUserMigrations(mg *Migrator) { // Service accounts login were not unique per org. this migration is part of making it unique per org // to be able to create service accounts that are unique per org - mg.AddMigration(user.AllowSameLoginCrossOrgs, &user.ServiceAccountsSameLoginCrossOrgs{}) + mg.AddMigration(usermig.AllowSameLoginCrossOrgs, &usermig.ServiceAccountsSameLoginCrossOrgs{}) + + // Users login and email should be in lower case + mg.AddMigration(usermig.LowerCaseUserLoginAndEmail, &usermig.UsersLowerCaseLoginAndEmail{}) } const migSQLITEisServiceAccountNullable = `ALTER TABLE user ADD COLUMN tmp_service_account BOOLEAN DEFAULT 0; diff --git a/pkg/services/sqlstore/migrations/user/service_account_multiple_org_login_migrator.go b/pkg/services/sqlstore/migrations/usermig/service_account_multiple_org_login_migrator.go similarity index 99% rename from pkg/services/sqlstore/migrations/user/service_account_multiple_org_login_migrator.go rename to pkg/services/sqlstore/migrations/usermig/service_account_multiple_org_login_migrator.go index 064985c0f73..1d60a4f7f28 100644 --- a/pkg/services/sqlstore/migrations/user/service_account_multiple_org_login_migrator.go +++ b/pkg/services/sqlstore/migrations/usermig/service_account_multiple_org_login_migrator.go @@ -1,4 +1,4 @@ -package user +package usermig import ( "fmt" diff --git a/pkg/services/sqlstore/migrations/user/test/service_account_test.go b/pkg/services/sqlstore/migrations/usermig/test/service_account_test.go similarity index 98% rename from pkg/services/sqlstore/migrations/user/test/service_account_test.go rename to pkg/services/sqlstore/migrations/usermig/test/service_account_test.go index eab2f5ac444..043c9852245 100644 --- a/pkg/services/sqlstore/migrations/user/test/service_account_test.go +++ b/pkg/services/sqlstore/migrations/usermig/test/service_account_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/log" - usermig "github.com/grafana/grafana/pkg/services/sqlstore/migrations/user" + "github.com/grafana/grafana/pkg/services/sqlstore/migrations/usermig" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" diff --git a/pkg/services/sqlstore/migrations/usermig/test/user_lowercase_login_and_email_test.go b/pkg/services/sqlstore/migrations/usermig/test/user_lowercase_login_and_email_test.go new file mode 100644 index 00000000000..33e206ca38c --- /dev/null +++ b/pkg/services/sqlstore/migrations/usermig/test/user_lowercase_login_and_email_test.go @@ -0,0 +1,253 @@ +package test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/sqlstore/migrations/usermig" + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/setting" +) + +func TestLowerCaseMigration(t *testing.T) { + type migrationTestCase struct { + desc string + users []*user.User + wantUsers []*user.User + } + testCases := []migrationTestCase{ + { + desc: "basic case updates login and email to lowercase", + users: []*user.User{ + { + ID: 1, + UID: "u1", + Login: "User1", + Email: "USER1@domain.com", + Name: "user1", + OrgID: 1, + Created: now, + Updated: now, + }, + { + ID: 2, + UID: "u2", + Login: "User2", + Email: "USER2@domain.com", + Name: "user2", + OrgID: 1, + Created: now, + Updated: now, + }, + }, + wantUsers: []*user.User{ + { + ID: 1, + Login: "user1", + Email: "user1@domain.com", + }, + { + ID: 2, + Login: "user2", + Email: "user2@domain.com", + }, + }, + }, + // "2 users - same login, one already lowercase" + { + desc: "2 users with same login one already has lowercase so we keep both", + users: []*user.User{ + { + ID: 1, + UID: "u1", + Login: "user1", + Email: "user1@email.com", + Name: "user1", + OrgID: 1, + Created: now, + Updated: now, + }, + { + ID: 2, + UID: "u2", + Login: "User1", + Email: "user1-new@email.com", + Name: "user2", + OrgID: 1, + Created: now, + Updated: now, + }, + }, + wantUsers: []*user.User{ + { + ID: 1, + Login: "user1", + Email: "user1@email.com", + }, + { + ID: 2, + Login: "User1", + Email: "user1-new@email.com", + }, + }, + }, + // "2 users - same login, one already lowercase" + { + desc: "2 users with same login one already has lowercase so we keep both case for uppercasing comes first in our loop", + users: []*user.User{ + { + ID: 1, + UID: "u1", + Login: "User1", + Email: "user1@email.com", + Name: "user1", + OrgID: 1, + Created: now, + Updated: now, + }, + { + ID: 2, + UID: "u2", + Login: "user1", + Email: "user1-new@email.com", + Name: "user2", + OrgID: 1, + Created: now, + Updated: now, + }, + }, + wantUsers: []*user.User{ + { + ID: 1, + Login: "User1", + Email: "user1@email.com", + }, + { + ID: 2, + Login: "user1", + Email: "user1-new@email.com", + }, + }, + }, + // "2 users - same email, one already lowercase" + { + desc: "2 users with same email one already has lowercase so we keep both", + users: []*user.User{ + { + ID: 1, + UID: "u1", + Login: "user1", + Email: "USER1@email.com", + Name: "user1", + OrgID: 1, + Created: now, + Updated: now, + }, + { + ID: 2, + UID: "u2", + Login: "user1-new-login", + Email: "user1@email.com", + Name: "user2", + OrgID: 1, + Created: now, + Updated: now, + }, + }, + wantUsers: []*user.User{ + { + ID: 1, + Login: "user1", + Email: "USER1@email.com", + }, + { + ID: 2, + Login: "user1-new-login", + Email: "user1@email.com", + }, + }, + }, + + // "2 users - same login, none lowercase" + { + desc: "2 users with same login noone is lowercased we pick the most recent user to lowercase", + users: []*user.User{ + { + ID: 1, + UID: "u1", + Login: "USER1", + Email: "user1@mail.com", + Name: "user1", + OrgID: 1, + Created: now.Add(-1 * time.Hour), + Updated: now.Add(-1 * time.Hour), + }, + { + ID: 2, + UID: "u2", + Login: "User1", + Email: "user1-new@mail.com", + Name: "user2", + OrgID: 1, + Created: now, + Updated: now, + }, + }, + wantUsers: []*user.User{ + { + ID: 1, + Login: "user1", + Email: "user1@mail.com", + }, + { + ID: 2, + Login: "User1", + Email: "user1-new@mail.com", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + // Run initial migration to have a working DB + x := setupTestDB(t) + // Remove migration + _, errDeleteMig := x.Exec(`DELETE FROM migration_log WHERE migration_id = ?`, usermig.LowerCaseUserLoginAndEmail) + require.NoError(t, errDeleteMig) + + // insert users + usersCount, err := x.Insert(tc.users) + require.NoError(t, err) + require.Equal(t, int64(len(tc.users)), usersCount) + + // run the migration + usermigrator := migrator.NewMigrator(x, &setting.Cfg{Logger: log.New("usermigration.test")}) + usermig.AddLowerCaseUserLoginAndEmail(usermigrator) + errRunningMig := usermigrator.Start(false, 0) + require.NoError(t, errRunningMig) + + // Check users + resultingUsers := []user.User{} + err = x.Table("user").Find(&resultingUsers) + require.NoError(t, err) + + // Check that the users have been updated + require.Equal(t, len(tc.wantUsers), len(resultingUsers)) + + for i := range tc.wantUsers { + for _, u := range resultingUsers { + if u.ID == tc.wantUsers[i].ID { + assert.Equal(t, tc.wantUsers[i].Login, u.Login) + assert.Equal(t, tc.wantUsers[i].Email, u.Email) + } + } + } + }) + } +} diff --git a/pkg/services/sqlstore/migrations/user/test/user_test.go b/pkg/services/sqlstore/migrations/usermig/test/user_test.go similarity index 100% rename from pkg/services/sqlstore/migrations/user/test/user_test.go rename to pkg/services/sqlstore/migrations/usermig/test/user_test.go diff --git a/pkg/services/sqlstore/migrations/usermig/user_lowercase_login_and_email.go b/pkg/services/sqlstore/migrations/usermig/user_lowercase_login_and_email.go new file mode 100644 index 00000000000..570b79ba52e --- /dev/null +++ b/pkg/services/sqlstore/migrations/usermig/user_lowercase_login_and_email.go @@ -0,0 +1,97 @@ +package usermig + +import ( + "strings" + + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/services/user" + "xorm.io/xorm" +) + +const ( + LowerCaseUserLoginAndEmail = "update login and email fields to lowercase" +) + +// AddLowerCaseUserLoginAndEmail adds a migration that updates the login and email fields of all users to be in lower case. +func AddLowerCaseUserLoginAndEmail(mg *migrator.Migrator) { + mg.AddMigration(LowerCaseUserLoginAndEmail, &UsersLowerCaseLoginAndEmail{}) +} + +var _ migrator.CodeMigration = new(UsersLowerCaseLoginAndEmail) + +type UsersLowerCaseLoginAndEmail struct { + migrator.MigrationBase +} + +func (p *UsersLowerCaseLoginAndEmail) SQL(dialect migrator.Dialect) string { + return "code migration" +} + +func (p *UsersLowerCaseLoginAndEmail) Exec(sess *xorm.Session, mg *migrator.Migrator) error { + // Get all users + users := make([]*user.User, 0) + err := sess.Table("user").Find(&users) + if err != nil { + return err + } + processedLogins := make(map[string]bool) + processedEmails := make(map[string]bool) + + for _, usr := range users { + /* + LOGIN + */ + lowerLogin := strings.ToLower(usr.Login) + // only work through if login is not already in lower case + if usr.Login != lowerLogin && !processedLogins[lowerLogin] { + // Check if lower login exists + existingLowerCasedUserLogin := &user.User{} + + // lowercaseexists in database + hasLowerCasedLogin, err := sess.Table("user").Where("login = ?", lowerLogin).Get(existingLowerCasedUserLogin) + if err != nil { + return err + } + + // If exact login does not exist and lower case login does not exist, update the user's login to be in lower case + if !hasLowerCasedLogin { + uLogin := user.User{ + Name: usr.Name, + Login: lowerLogin, + } + _, err := sess.ID(usr.ID).Update(&uLogin) + if err != nil { + return err + } + } + } + processedLogins[lowerLogin] = true + + /* + EMAIL + */ + lowerEmail := strings.ToLower(usr.Email) + // only work through if email is not already in lower case + if usr.Email != lowerEmail && !processedEmails[lowerEmail] { + // Check if lower case email exists + existingUserEmail := &user.User{} + hasLowerCasedEmail, err := sess.Table("user").Where("email = ?", lowerEmail).Get(existingUserEmail) + if err != nil { + return err + } + // If lower case email does not exist, update the user's email to be in lower case + if !hasLowerCasedEmail { + uEmail := user.User{ + Name: usr.Name, + Email: lowerEmail, + } + _, err := sess.ID(usr.ID).Update(&uEmail) + if err != nil { + return err + } + } + } + processedEmails[lowerEmail] = true + } + return nil +} diff --git a/pkg/services/user/userimpl/store.go b/pkg/services/user/userimpl/store.go index c0bc043e499..ec8b6c8cf08 100644 --- a/pkg/services/user/userimpl/store.go +++ b/pkg/services/user/userimpl/store.go @@ -90,9 +90,10 @@ func (ss *sqlStore) Insert(ctx context.Context, cmd *user.User) (int64, error) { func (ss *sqlStore) Get(ctx context.Context, usr *user.User) (*user.User, error) { ret := &user.User{} err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { - login := usr.Login - email := usr.Email - where := "LOWER(email)=LOWER(?) OR LOWER(login)=LOWER(?)" + // enforcement of lowercase due to forcement of caseinsensitive login + login := strings.ToLower(usr.Login) + email := strings.ToLower(usr.Email) + where := "email=? OR login=?" exists, err := sess.Where(where, email, login).Get(ret) if !exists { @@ -178,6 +179,9 @@ func (ss *sqlStore) CaseInsensitiveLoginConflict(ctx context.Context, login, ema } func (ss *sqlStore) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) { + // enforcement of lowercase due to forcement of caseinsensitive login + query.LoginOrEmail = strings.ToLower(query.LoginOrEmail) + usr := &user.User{} err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { if query.LoginOrEmail == "" { @@ -191,7 +195,7 @@ func (ss *sqlStore) GetByLogin(ctx context.Context, query *user.GetUserByLoginQu // Since username can be an email address, attempt login with email address // first if the login field has the "@" symbol. if strings.Contains(query.LoginOrEmail, "@") { - where = "LOWER(email)=LOWER(?)" + where = "email=?" has, err = sess.Where(ss.notServiceAccountFilter()).Where(where, query.LoginOrEmail).Get(usr) if err != nil { return err @@ -200,7 +204,7 @@ func (ss *sqlStore) GetByLogin(ctx context.Context, query *user.GetUserByLoginQu // Look for the login field instead of email if !has { - where = "LOWER(login)=LOWER(?)" + where = "login=?" has, err = sess.Where(ss.notServiceAccountFilter()).Where(where, query.LoginOrEmail).Get(usr) } @@ -209,9 +213,6 @@ func (ss *sqlStore) GetByLogin(ctx context.Context, query *user.GetUserByLoginQu } else if !has { return user.ErrUserNotFound } - if err := ss.userCaseInsensitiveLoginConflict(ctx, sess, usr.Login, usr.Email); err != nil { - return err - } return nil }) @@ -223,13 +224,16 @@ func (ss *sqlStore) GetByLogin(ctx context.Context, query *user.GetUserByLoginQu } func (ss *sqlStore) GetByEmail(ctx context.Context, query *user.GetUserByEmailQuery) (*user.User, error) { + // enforcement of lowercase due to forcement of caseinsensitive login + query.Email = strings.ToLower(query.Email) + usr := &user.User{} err := ss.db.WithDbSession(ctx, func(sess *db.Session) error { if query.Email == "" { return user.ErrUserNotFound } - where := "LOWER(email)=LOWER(?)" + where := "email=?" has, err := sess.Where(ss.notServiceAccountFilter()).Where(where, query.Email).Get(usr) if err != nil { @@ -237,10 +241,6 @@ func (ss *sqlStore) GetByEmail(ctx context.Context, query *user.GetUserByEmailQu } else if !has { return user.ErrUserNotFound } - - if err := ss.userCaseInsensitiveLoginConflict(ctx, sess, usr.Login, usr.Email); err != nil { - return err - } return nil }) if err != nil { @@ -249,21 +249,6 @@ func (ss *sqlStore) GetByEmail(ctx context.Context, query *user.GetUserByEmailQu return usr, nil } -func (ss *sqlStore) userCaseInsensitiveLoginConflict(ctx context.Context, sess *db.Session, login, email string) error { - users := make([]user.User, 0) - - if err := sess.Where("LOWER(email)=LOWER(?) OR LOWER(login)=LOWER(?)", - email, login).Find(&users); err != nil { - return err - } - - if len(users) > 1 { - return &user.ErrCaseInsensitiveLoginConflict{Users: users} - } - - return nil -} - // LoginConflict returns an error if the provided email or login are already // associated with a user. If caseInsensitive is true the search is not case // sensitive. @@ -299,6 +284,10 @@ func (ss *sqlStore) loginConflict(ctx context.Context, sess *db.Session, login, } func (ss *sqlStore) Update(ctx context.Context, cmd *user.UpdateUserCommand) error { + // enforcement of lowercase due to forcement of caseinsensitive login + cmd.Login = strings.ToLower(cmd.Login) + cmd.Email = strings.ToLower(cmd.Email) + return ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { user := user.User{ Name: cmd.Name, diff --git a/pkg/services/user/userimpl/store_test.go b/pkg/services/user/userimpl/store_test.go index 6803ff4ce01..5cc3731c2a7 100644 --- a/pkg/services/user/userimpl/store_test.go +++ b/pkg/services/user/userimpl/store_test.go @@ -344,37 +344,6 @@ func TestIntegrationUserDataAccess(t *testing.T) { return nil }) require.NoError(t, err) - - t.Run("GetByEmail - email conflict", func(t *testing.T) { - query := user.GetUserByEmailQuery{Email: "confusertest@test.com"} - _, err = userStore.GetByEmail(context.Background(), &query) - require.Error(t, err) - }) - - t.Run("GetByEmail - login conflict", func(t *testing.T) { - query := user.GetUserByEmailQuery{Email: "user_test_login_conflict@test.com"} - _, err = userStore.GetByEmail(context.Background(), &query) - require.Error(t, err) - }) - - t.Run("GetByLogin - email conflict", func(t *testing.T) { - query := user.GetUserByLoginQuery{LoginOrEmail: "user_email_conflict_two"} - _, err = userStore.GetByLogin(context.Background(), &query) - require.Error(t, err) - }) - - t.Run("GetByLogin - login conflict", func(t *testing.T) { - query := user.GetUserByLoginQuery{LoginOrEmail: "user_test_login_conflict"} - _, err = userStore.GetByLogin(context.Background(), &query) - require.Error(t, err) - }) - - t.Run("GetByLogin - login conflict by email", func(t *testing.T) { - query := user.GetUserByLoginQuery{LoginOrEmail: "user_test_login_conflict@test.com"} - _, err = userStore.GetByLogin(context.Background(), &query) - require.Error(t, err) - }) - t.Run("GetByLogin - user2 uses user1.email as login", func(t *testing.T) { // create user_1 user1 := &user.User{ From 3397e8bf096731182cf6f99c6ee268299ea44199 Mon Sep 17 00:00:00 2001 From: Matthew Jacobson Date: Thu, 25 Apr 2024 13:36:00 -0400 Subject: [PATCH 119/222] Alerting: Improve error when receiver or time interval used by rule is deleted (#86865) * Alerting: Improve error when receiver used by rule is deleted * Remove RuleUID from public error and data * Improve fallback error in am config post * Refactor to expand to time intervals * Fix message on unchecked errors to be same as before --- pkg/services/ngalert/api/api_alertmanager.go | 3 ++- .../ngalert/notifier/alertmanager_config.go | 22 ++++++++++++++++ pkg/services/ngalert/notifier/validation.go | 26 ++++++++++++++++--- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index bdb3756de43..067077326cd 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -10,6 +10,7 @@ import ( "time" "github.com/go-openapi/strfmt" + alertingNotify "github.com/grafana/alerting/notify" "github.com/grafana/grafana/pkg/api/response" @@ -304,7 +305,7 @@ func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *contextmodel.ReqContext, b return response.Error(http.StatusConflict, err.Error(), err) } - return ErrResp(http.StatusInternalServerError, err, "") + return response.ErrOrFallback(http.StatusInternalServerError, err.Error(), err) } func (srv AlertmanagerSrv) RouteGetReceivers(c *contextmodel.ReqContext) response.Response { diff --git a/pkg/services/ngalert/notifier/alertmanager_config.go b/pkg/services/ngalert/notifier/alertmanager_config.go index 32ae92baea2..232f17a3899 100644 --- a/pkg/services/ngalert/notifier/alertmanager_config.go +++ b/pkg/services/ngalert/notifier/alertmanager_config.go @@ -13,6 +13,20 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/util/errutil" +) + +var ( + // ErrAlertmanagerReceiverInUse is primarily meant for when a receiver is used by a rule and is being deleted. + ErrAlertmanagerReceiverInUse = errutil.BadRequest("alerting.notifications.alertmanager.receiverInUse").MustTemplate("receiver [Name: {{ .Public.Receiver }}] is used by rule: {{ .Error }}", + errutil.WithPublic( + "receiver [Name: {{ .Public.Receiver }}] is used by rule", + )) + // ErrAlertmanagerTimeIntervalInUse is primarily meant for when a time interval is used by a rule and is being deleted. + ErrAlertmanagerTimeIntervalInUse = errutil.BadRequest("alerting.notifications.alertmanager.intervalInUse").MustTemplate("time interval [Name: {{ .Public.Interval }}] is used by rule: {{ .Error }}", + errutil.WithPublic( + "time interval [Name: {{ .Public.Interval }}] is used by rule", + )) ) type UnknownReceiverError struct { @@ -227,6 +241,14 @@ func (moa *MultiOrgAlertmanager) SaveAndApplyAlertmanagerConfiguration(ctx conte if err := am.SaveAndApplyConfig(ctx, &config); err != nil { moa.logger.Error("Unable to save and apply alertmanager configuration", "error", err) + errReceiverDoesNotExist := ErrorReceiverDoesNotExist{} + if errors.As(err, &errReceiverDoesNotExist) { + return ErrAlertmanagerReceiverInUse.Build(errutil.TemplateData{Public: map[string]interface{}{"Receiver": errReceiverDoesNotExist.Reference}, Error: err}) + } + errTimeIntervalDoesNotExist := ErrorTimeIntervalDoesNotExist{} + if errors.As(err, &errTimeIntervalDoesNotExist) { + return ErrAlertmanagerTimeIntervalInUse.Build(errutil.TemplateData{Public: map[string]interface{}{"Interval": errTimeIntervalDoesNotExist.Reference}, Error: err}) + } return AlertmanagerConfigRejectedError{err} } diff --git a/pkg/services/ngalert/notifier/validation.go b/pkg/services/ngalert/notifier/validation.go index 48e0f20d2ad..835f2f10934 100644 --- a/pkg/services/ngalert/notifier/validation.go +++ b/pkg/services/ngalert/notifier/validation.go @@ -6,13 +6,33 @@ import ( "fmt" "sync" + "github.com/prometheus/alertmanager/config" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/store" - "github.com/prometheus/alertmanager/config" ) +type ErrorReferenceInvalid struct { + Reference string +} + +type ErrorReceiverDoesNotExist struct { + ErrorReferenceInvalid +} +type ErrorTimeIntervalDoesNotExist struct { + ErrorReferenceInvalid +} + +func (e ErrorReceiverDoesNotExist) Error() string { + return fmt.Sprintf("receiver %s does not exist", e.Reference) +} + +func (e ErrorTimeIntervalDoesNotExist) Error() string { + return fmt.Sprintf("time interval %s does not exist", e.Reference) +} + // NotificationSettingsValidator validates NotificationSettings against the current Alertmanager configuration type NotificationSettingsValidator interface { Validate(s models.NotificationSettings) error @@ -64,11 +84,11 @@ func (n staticValidator) Validate(settings models.NotificationSettings) error { } var errs []error if _, ok := n.availableReceivers[settings.Receiver]; !ok { - errs = append(errs, fmt.Errorf("receiver '%s' does not exist", settings.Receiver)) + errs = append(errs, ErrorReceiverDoesNotExist{ErrorReferenceInvalid: ErrorReferenceInvalid{Reference: settings.Receiver}}) } for _, interval := range settings.MuteTimeIntervals { if _, ok := n.availableTimeIntervals[interval]; !ok { - errs = append(errs, fmt.Errorf("mute time interval '%s' does not exist", interval)) + errs = append(errs, ErrorTimeIntervalDoesNotExist{ErrorReferenceInvalid: ErrorReferenceInvalid{Reference: interval}}) } } return errors.Join(errs...) From d1b67847a249d0dcb9da440a5e9dfdeb4e1f0242 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Thu, 25 Apr 2024 12:51:07 -0500 Subject: [PATCH 120/222] Migrations: Graph (old) percent stacked (#84335) --- public/app/plugins/panel/timeseries/migrations.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/panel/timeseries/migrations.ts b/public/app/plugins/panel/timeseries/migrations.ts index c27205c84a6..edb9eba6a57 100644 --- a/public/app/plugins/panel/timeseries/migrations.ts +++ b/public/app/plugins/panel/timeseries/migrations.ts @@ -339,9 +339,22 @@ export function graphToTimeseriesOptions(angular: any): { if (angular.stack) { graph.stacking = { - mode: StackingMode.Normal, + mode: angular.percentage ? StackingMode.Percent : StackingMode.Normal, group: defaultGraphConfig.stacking!.group, }; + + if (angular.percentage) { + if (angular.yaxis) { + delete y1.min; + delete y1.max; + + // TimeSeries currently uses 0-1 for percent, so allowing zero leaves only top and bottom ticks. + // removing it feels better. probably should fix in TimeSeries, but let's kick it down the road + if (y1.decimals === 0) { + delete y1.decimals; + } + } + } } y1.custom = omitBy(graph, isNil); From 9f07c37face21e36e59b28093d2e313dcad55b3c Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Thu, 25 Apr 2024 15:38:12 -0300 Subject: [PATCH 121/222] ShareModal: Fix share link tracking (#86940) --- public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx index 1f7bd26eacb..cf2e28c8299 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx @@ -126,13 +126,13 @@ export class ShareLinkTab extends SceneObjectBase { return this.state.shareUrl; }; - onCopy() { + onCopy = () => { DashboardInteractions.shareLinkCopied({ currentTimeRange: this.state.useLockedTime, theme: this.state.selectedTheme, shortenURL: this.state.useShortUrl, }); - } + }; } function ShareLinkTabRenderer({ model }: SceneComponentProps) { From fbcb9a3677905cd8d44735e4f11f156362244425 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 25 Apr 2024 20:52:09 +0200 Subject: [PATCH 122/222] Chore: Upgrade/fix deps after thema removal (#86763) * Chore: Upgrade/fix deps after thema removal * github.com/deepmap/oapi-codegen v1.14.0 * github.com/deepmap/oapi-codegen v1.15.0 * github.com/deepmap/oapi-codegen v1.16.0 * fix for dep used in enterprise * github.com/deepmap/oapi-codegen/v2 v2.0.0 * oapi-codegen/v2 v2.1.0, kin-openapi v0.122.0, sdk * keep kin-openapi at v0.122.0 and allow SDK to use v0.124.0 * remove github.com/deepmap/oapi-codegen v1 dep * fix owner * add back github.com/deepmap/oapi-codegen v1 dep * upgrade github.com/influxdata/influxdb-client-go/v2 to get rid of deepmap/oapi-codegen * migrate to oapi-codegen/runtime * sdk * sdk v0.227.0 --- go.mod | 43 +++---- go.sum | 79 ++++-------- go.work.sum | 118 +++++++++++++++--- pkg/apimachinery/go.mod | 1 + pkg/apimachinery/go.sum | 3 +- pkg/apiserver/go.mod | 6 +- pkg/apiserver/go.sum | 12 +- pkg/codegen/generators/go_generator.go | 6 +- pkg/extensions/main.go | 1 + .../backendplugin/grpcplugin/log_wrapper.go | 5 + pkg/promlib/go.mod | 6 +- pkg/promlib/go.sum | 11 +- pkg/util/xorm/go.mod | 2 +- pkg/util/xorm/go.sum | 3 +- 14 files changed, 168 insertions(+), 128 deletions(-) diff --git a/go.mod b/go.mod index c895906637c..20f03fd1a38 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,9 @@ replace cuelang.org/go => github.com/grafana/cue v0.0.0-20230926092038-971951014 // import that instead of v0.X even though v0.X is newer. replace github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.49.0 +// Update when github.com/deepmap/oapi-codegen/v2 is updated to support later versions. +replace github.com/getkin/kin-openapi => github.com/getkin/kin-openapi v0.122.0 + require ( cloud.google.com/go/storage v1.37.0 // @grafana/grafana-backend-group cuelang.org/go v0.6.0-0.dev // @grafana/grafana-as-code @@ -51,20 +54,20 @@ require ( github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code github.com/grafana/grafana-aws-sdk v0.25.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.0.1 // @grafana/partner-datasources - github.com/grafana/grafana-plugin-sdk-go v0.226.0 // @grafana/plugins-platform-backend + github.com/grafana/grafana-plugin-sdk-go v0.227.0 // @grafana/plugins-platform-backend github.com/hashicorp/go-hclog v1.6.3 // @grafana/plugins-platform-backend github.com/hashicorp/go-plugin v1.6.0 // @grafana/plugins-platform-backend github.com/hashicorp/go-version v1.6.0 // @grafana/grafana-backend-group github.com/hashicorp/hcl/v2 v2.17.0 // @grafana/alerting-squad-backend - github.com/influxdata/influxdb-client-go/v2 v2.12.3 // @grafana/observability-metrics - github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // @grafana/grafana-app-platform-squad + github.com/influxdata/influxdb-client-go/v2 v2.13.0 // @grafana/observability-metrics + github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // @grafana/grafana-app-platform-squad github.com/jmespath/go-jmespath v0.4.0 // @grafana/grafana-backend-group github.com/json-iterator/go v1.1.12 // @grafana/grafana-backend-group github.com/lib/pq v1.10.9 // @grafana/grafana-backend-group github.com/linkedin/goavro/v2 v2.10.0 // @grafana/grafana-backend-group github.com/m3db/prometheus_remote_client_golang v0.4.4 // @grafana/grafana-backend-group github.com/magefile/mage v1.15.0 // @grafana/grafana-release-guild - github.com/mattn/go-isatty v0.0.19 // @grafana/grafana-backend-group + github.com/mattn/go-isatty v0.0.20 // @grafana/grafana-backend-group github.com/mattn/go-sqlite3 v1.14.19 // @grafana/grafana-backend-group github.com/matttproud/golang_protobuf_extensions v1.0.4 // @grafana/alerting-squad-backend github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // @grafana/grafana-operator-experience-squad @@ -108,7 +111,7 @@ require ( gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-squad-backend - xorm.io/builder v0.3.6 // indirect; @grafana/grafana-backend-group + xorm.io/builder v0.3.6 // @grafana/grafana-backend-group xorm.io/core v0.7.3 // @grafana/grafana-backend-group xorm.io/xorm v0.8.2 // @grafana/alerting-squad-backend ) @@ -130,7 +133,6 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect - github.com/deepmap/oapi-codegen v1.13.0 // @grafana/grafana-as-code github.com/dennwc/varint v1.0.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/go-units v0.5.0 // indirect @@ -280,7 +282,6 @@ require ( github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/agext/levenshtein v1.2.1 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect - github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/bmatcuk/doublestar v1.1.1 // indirect @@ -410,12 +411,9 @@ require ( github.com/imdario/mergo v0.3.16 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/labstack/echo/v4 v4.11.1 // indirect - github.com/labstack/gommon v0.4.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect github.com/wk8/go-ordered-map v1.0.0 // @grafana/grafana-backend-group github.com/xlab/treeprint v1.2.0 // @grafana/observability-traces-and-profiling go.opentelemetry.io/proto/otlp v1.1.0 // indirect @@ -466,10 +464,12 @@ require ( github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // @grafana/grafana-app-platform-squad ) -require github.com/getkin/kin-openapi v0.120.0 // @grafana/grafana-as-code +require github.com/getkin/kin-openapi v0.124.0 // @grafana/grafana-as-code require github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 // @grafana/identity-access-team +require github.com/deepmap/oapi-codegen/v2 v2.1.0 // @grafana/grafana-as-code + require github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // @grafana/plugins-platform-backend require github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // @grafana/grafana-backend-group @@ -477,17 +477,10 @@ require github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // @grafana/grafa require ( cloud.google.com/go/auth v0.2.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.1 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/bytedance/sonic v1.9.1 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/gin-gonic/gin v1.9.1 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/invopop/jsonschema v0.12.0 // indirect @@ -497,13 +490,10 @@ require ( github.com/jcmturner/goidentity/v6 v6.0.1 // indirect github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect - github.com/leodido/go-urn v1.2.4 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect + github.com/oapi-codegen/runtime v1.1.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect - golang.org/x/arch v0.3.0 // indirect ) // Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream @@ -511,11 +501,6 @@ replace github.com/crewjam/saml => github.com/grafana/saml v0.4.15-0.20231025143 // replace github.com/google/cel-go => github.com/google/cel-go v0.16.1 -// Thema's thema CLI requires cobra, which eventually works its way down to go-hclog@v1.0.0. -// Upgrading affects backend plugins: https://github.com/grafana/grafana/pull/47653#discussion_r850508593 -// No harm to Thema because it's only a dependency in its main package. -replace github.com/hashicorp/go-hclog => github.com/hashicorp/go-hclog v0.16.1 - // Use our fork of the upstream alertmanagers. // This is required in order to get notification delivery errors from the receivers API. replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b diff --git a/go.sum b/go.sum index 602a0a5d2d2..71cb467b0e0 100644 --- a/go.sum +++ b/go.sum @@ -1506,9 +1506,6 @@ github.com/buildkite/yaml v2.1.0+incompatible/go.mod h1:UoU8vbcwu1+vjZq01+KrpSeL github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/caio/go-tdigest v3.1.0+incompatible h1:uoVMJ3Q5lXmVLCCqaMGHLBWnbGoN6Lpu7OAUPR60cds= github.com/caio/go-tdigest v3.1.0+incompatible/go.mod h1:sHQM/ubZStBUmF1WbB8FAm8q9GjDajLC5T7ydxE3JHI= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= @@ -1532,9 +1529,6 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 h1:aPflPkRFkVwbW6dmcVqfgwp1i+UWGFH6VgR1Jim5Ygc= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= @@ -1618,8 +1612,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= -github.com/deepmap/oapi-codegen v1.13.0 h1:cnFHelhsRQbYvanCUAbRSn/ZpkUb1HPRlQcu8YqSORQ= -github.com/deepmap/oapi-codegen v1.13.0/go.mod h1:Amy7tbubKY9qkZOXqymI3Z6xSbndmu+atMJheLdyg44= +github.com/deepmap/oapi-codegen/v2 v2.1.0 h1:I/NMVhJCtuvL9x+S2QzZKpSjGi33oDZwPRdemvOZWyQ= +github.com/deepmap/oapi-codegen/v2 v2.1.0/go.mod h1:R1wL226vc5VmCNJUvMyYr3hJMm5reyv25j952zAVXZ8= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= @@ -1745,21 +1739,16 @@ github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyT github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas= github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y= github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34= -github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= -github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10= +github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= @@ -1867,18 +1856,10 @@ github.com/go-openapi/validate v0.23.0/go.mod h1:EeiAZ5bmpSIOJV1WLfyYF9qp/B1ZgSa github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4= @@ -2024,8 +2005,6 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC5IXdEA547bcoU5dozgQAfc8Onsg4= -github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= @@ -2194,8 +2173,8 @@ github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkr github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ= github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= -github.com/grafana/grafana-plugin-sdk-go v0.226.0 h1:PDnxWbQDn9GXfp62MH604GZ73j0fsyxyrDhpm08N5vY= -github.com/grafana/grafana-plugin-sdk-go v0.226.0/go.mod h1:j5TwvdShpKdgWgE4Tvk30c5bO9tKhO5wjZ1xwGhFBQg= +github.com/grafana/grafana-plugin-sdk-go v0.227.0 h1:xkARhSnCovkcDd0n8uwingJID4fAn8tKX7nR2M22ML8= +github.com/grafana/grafana-plugin-sdk-go v0.227.0/go.mod h1:ZhVLifkf1Yyt/I9XjwznANdMGBL2u7/dH/ihyIZ9EA0= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 h1:hpyusz8c3yRFoJPlA0o34rWnsLbaOOBZleqRhFBi5Lg= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4/go.mod h1:vrRQJuNprTWqwm6JPxHf3BoTJhvO15QMEjQ7Q/YUOnI= github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 h1:tIbI5zgos92vwJ8lV3zwHwuxkV03GR3FGLkFW9V5LxY= @@ -2259,8 +2238,14 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o= -github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -2349,12 +2334,12 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb v1.7.6/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= -github.com/influxdata/influxdb-client-go/v2 v2.12.3 h1:28nRlNMRIV4QbtIUvxhWqaxn0IpXeMSkY/uJa/O/vC4= -github.com/influxdata/influxdb-client-go/v2 v2.12.3/go.mod h1:IrrLUbCjjfkmRuaCiGQg4m2GbkaeJDcuWoxiWdQEbA0= +github.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM= +github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 h1:vilfsDSy7TDxedi9gyBkMvAirat/oRcL0lFdJBf6tdM= -github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU= +github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= @@ -2505,16 +2490,10 @@ github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLg github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= -github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 h1:X/79QL0b4YJVO5+OsPH9rF2u428CIrGL/jLmPsoOQQ4= github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -2559,7 +2538,6 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -2577,8 +2555,9 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= @@ -2701,6 +2680,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= @@ -2784,8 +2765,6 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= @@ -2915,8 +2894,9 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= @@ -3045,14 +3025,13 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1 github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f h1:A+MmlgpvrHLeUP8dkBVn4Pnf5Bp5Yk2OALm7SEJLLE8= github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f/go.mod h1:OBcG9bn7sHtXgarhUEb3OfCnNsgtGnkVf41ilSZ3K3E= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= @@ -3072,9 +3051,6 @@ github.com/urfave/cli/v2 v2.25.0 h1:ykdZKuQey2zq0yin/l7JOm9Mh+pg72ngYMeB0ABn6q8= github.com/urfave/cli/v2 v2.25.0/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae h1:oyiy3uBj1F4O3AaFh7hUGBrJjAssJhKyAbwxtkslxqo= github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae/go.mod h1:PnwzbSst7KD3vpBzzlntZU5gjVa455Uqa5QPiKSYJzQ= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= @@ -3258,9 +3234,6 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= gocloud.dev v0.25.0 h1:Y7vDq8xj7SyM848KXf32Krda2e6jQ4CLh/mTeCSqXtk= gocloud.dev v0.25.0/go.mod h1:7HegHVCYZrMiU3IE1qtnzf/vRrDwLYnRNR3EhWX8x9Y= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -3360,6 +3333,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -3654,7 +3628,6 @@ golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/go.work.sum b/go.work.sum index 322a750dfb5..956533c2c4e 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,4 +1,3 @@ -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1 h1:vp9EaPFSb75qe/793x58yE5fY1IJ/gdxb/kcDUzavtI= buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1/go.mod h1:YDq2B5X5BChU0lxAG5MxHpDb8mx1fv9OGtF2mwOe7hY= buf.build/gen/go/grpc-ecosystem/grpc-gateway/protocolbuffers/go v1.28.1-20221127060915-a1ecdc58eccd.4 h1:z3Xc9n8yZ5k/Xr4ZTuff76TAYP20dWy7ZBV4cGIpbkM= @@ -387,10 +386,14 @@ github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LM github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0 h1:1PwO5w5VCtlUUl+KTOBsTGZlhjWkcybsGaAau52tOy8= +github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= +github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0 h1:YNu23BtH0PKF+fg3ykSorCp6jSTjcEtfnYLzbmcjVRA= -github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= +github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= +github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.6 h1:U68crOE3y3MPttCMQGywZOLrTeF5HHJ3/vDBCJn9/bA= @@ -401,12 +404,13 @@ github.com/RaveNoX/go-jsoncommentstrip v1.0.0 h1:t527LHHE3HmiHrq74QMpNPZpGCIJzTx github.com/RoaringBitmap/gocroaring v0.4.0 h1:5nufXUgWpBEUNEJXw7926YAA58ZAQRpWPrQV1xCoSjc= github.com/RoaringBitmap/real-roaring-datasets v0.0.0-20190726190000-eb7c87156f76 h1:ZYlhPbqQFU+AHfgtCdHGDTtRW1a8geZyiE8c6Q+Sl1s= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398 h1:WDC6ySpJzbxGWFh4aMxFFC28wwGp5pEuoTtvA4q/qQ4= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= github.com/Shopify/sarama v1.38.1 h1:lqqPUPQZ7zPqYlWpTh+LQ9bhYNu2xJL6k1SJN4WVe2A= github.com/Shopify/sarama v1.38.1/go.mod h1:iwv9a67Ha8VNa+TifujYoWGxWnu2kNVAQdSdZ4X2o5g= github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw= -github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19 h1:iXUgAaqDcIUGbRoy2TdeofRG/j1zpGRSEmNK05T+bi8= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= @@ -415,7 +419,6 @@ github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjH github.com/alecthomas/kong v0.2.11 h1:RKeJXXWfg9N47RYfMm0+igkxBCTF4bzbneAxaqid0c4= github.com/alecthomas/kong v0.2.11/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= github.com/alecthomas/participle/v2 v2.1.0 h1:z7dElHRrOEEq45F2TG5cbQihMtNTv8vwldytDj7Wrz4= -github.com/alecthomas/participle/v2 v2.1.0/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= @@ -440,17 +443,22 @@ github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4 h1:EmIEXOjAdXtxa2OGM github.com/aws/aws-sdk-go-v2/service/sns v1.17.4 h1:7TdmoJJBwLFyakXjfrGztejwY5Ie1JEto7YFfznCmAw= github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3 h1:uHjK81fESbGy2Y9lspub1+C6VN5W2UXTDo2A/Pm4G0U= github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1 h1:zc1YLcknvxdW/i1MuJKmEnFB2TNkOfguuQaGRvJXPng= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible h1:Ppm0npCCsmuR9oQaBtRuZcmILVE74aXE+AmrJj8L2ns= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= -github.com/bufbuild/protovalidate-go v0.2.1/go.mod h1:e7XXDtlxj5vlEyAgsrxpzayp4cEMKCSSb8ZCkin+MVA= github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw= +github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= @@ -497,13 +505,19 @@ github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= +github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/denisenkom/go-mssqldb v0.12.0 h1:VtrkII767ttSPNRfFekePK3sctr+joXgO58stqQbtUA= github.com/devigned/tab v0.1.1 h1:3mD6Kb1mUOYeLpJvTVSDwSg5ZsfSxfvxGRTxRsJsITA= github.com/dgraph-io/badger v1.6.0 h1:DshxFxZWXUcO0xX476VJC07Xsr6ZCBVRHKZ93Oh7Evo= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dhui/dktest v0.3.0 h1:kwX5a7EkLcjo7VpsPQSYJcKGbXBXdjI9FGjuUj1jn6I= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/drone/drone-runtime v1.1.0 h1:IsKbwiLY6+ViNBzX0F8PERJVZZcEJm9rgxEh3uZP5IE= github.com/drone/drone-runtime v1.1.0/go.mod h1:+osgwGADc/nyl40J0fdsf8Z09bgcBZXvXXnLOY48zYs= @@ -524,20 +538,29 @@ github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1S github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 h1:DddqAaWDpywytcG8w/qoQ5sAN8X12d3Z3koB0C3Rxsc= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= +github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2 h1:cZqz+yOJ/R64LcKjNQOdARott/jP7BnUQ9Ah7KaZCvw= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 h1:a9ENSRDFBUPkJ5lCgVZh26+ZbGyoVJG7yb5SSzF5H54= github.com/fsouza/fake-gcs-server v1.7.0 h1:Un0BXUXrRWYSmYyC1Rqm2e2WJfTPyDy/HGMz31emTi8= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ= github.com/go-fonts/latin-modern v0.2.0 h1:5/Tv1Ek/QCr20C6ZOz15vw3g7GELYL98KWr8Hgo+3vk= github.com/go-fonts/liberation v0.2.0 h1:jAkAWJP4S+OsrPLZM4/eC9iW7CtHy+HBXrEwZXWo5VM= @@ -551,14 +574,21 @@ github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 h1:6zl3BbBhdnMkpSj2 github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= +github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd h1:hSkbZ9XSyjyBirMeqSqUrK+9HboWrweVlzRNqoBi2d4= github.com/gobuffalo/depgen v0.1.0 h1:31atYa/UW9V5q8vMJ+W6wd64OaaTHUrCUXER358zLM4= github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= @@ -574,15 +604,20 @@ github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5 github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= +github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54= -github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4 h1:vF83LI8tAakwEwvWZtrIEx7pOySacl2TOxx6eXk4ePo= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/gofiber/fiber/v2 v2.46.0 h1:wkkWotblsGVlLjXj2dpgKQAYHtXumsK/HyFugQM68Ns= github.com/gofiber/fiber/v2 v2.46.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc= +github.com/gofiber/fiber/v2 v2.49.1/go.mod h1:nPUeEBUeeYGgwbDm59Gp7vS8MDyScL6ezr/Np9A13WU= +github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC5IXdEA547bcoU5dozgQAfc8Onsg4= +github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4= +github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/google/go-jsonnet v0.18.0 h1:/6pTy6g+Jh1a1I2UMoAODkqELFiVIdOxbNwv0DDzoOg= @@ -597,9 +632,10 @@ github.com/google/subcommands v1.0.1 h1:/eqq+otEXm5vhfBrbREPCSVQbvofip6kIz+mX5TU github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 h1:tlyzajkF3030q6M8SvmJSemC9DTHL/xaMa18b65+JM4= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grafana/authlib v0.0.0-20240319083410-9d4a6e3861e5/go.mod h1:86rRD5P6u2JPWtNWTMOlqlU+YMv2fUvVz/DomA6L7w4= github.com/grafana/dataplane/sdata v0.0.7 h1:CImITypIyS1jxijCR6xqKx71JnYAxcwpH9ChK0gH164= github.com/grafana/dataplane/sdata v0.0.7/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= @@ -615,7 +651,6 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWet github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= -github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= @@ -626,14 +661,17 @@ github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91 h1:KyZDvZ/G github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU= -github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= github.com/influxdata/influxdb v1.7.6 h1:8mQ7A/V+3noMGCt/P9pD09ISaiz9XvgCk303UYA3gcs= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig= +github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/iris-contrib/blackfriday v2.0.0+incompatible h1:o5sHQHHm0ToHUlAJSTjW9UWicjJSDDauOOQ2AHuIVp4= github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3pB80+OQg2qf6c8BfWE= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/jade v1.1.3 h1:p7J/50I0cjo0wq/VWVCDFd8taPJbuFC+bq23SniRFX0= github.com/iris-contrib/pongo2 v0.0.1 h1:zGP7pW51oi5eQZMIlGA3I+FHY9/HOQWDB+572yin0to= github.com/iris-contrib/schema v0.0.1 h1:10g/WnoRR+U+XXHWKBHeNy/+tZmM2kcAVGLOsz+yaDA= +github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= +github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= @@ -662,14 +700,35 @@ github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o= github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= +github.com/kataras/blocks v0.0.7 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4= +github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= +github.com/kataras/golog v0.1.9 h1:vLvSDpP7kihFGKFAvBSofYo7qZNULYSHOH2D7rPTKJk= +github.com/kataras/golog v0.1.9/go.mod h1:jlpk/bOaYCyqDqH18pgDHdaJab72yBE6i0O3s30hpWY= +github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9 h1:Vx8kDVhO2qepK8w44lBtp+RzN3ld743i+LYPzODJSpQ= +github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9/go.mod h1:ldkoR3iXABBeqlTibQ3MYaviA1oSlPvim6f55biwBh4= +github.com/kataras/jwt v0.1.10/go.mod h1:xkimAtDhU/aGlQqjwvgtg+VyuPwMiyZHaY8LJRh0mYo= +github.com/kataras/neffos v0.0.22/go.mod h1:IIJZcUDvwBxJGlDj942dqQgyznVKYDti91f8Ez+RRxE= +github.com/kataras/pio v0.0.12 h1:o52SfVYauS3J5X08fNjlGS5arXHjW/ItLkyLcKjoH6w= +github.com/kataras/pio v0.0.12/go.mod h1:ODK/8XBhhQ5WqrAhKy+9lTPS7sBf6O3KcLhc9klfRcY= +github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= +github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= +github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= +github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= github.com/kshvakov/clickhouse v1.3.5 h1:PDTYk9VYgbjPAWry3AoDREeMgOVUFij6bh6IjlloHL0= +github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= +github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= @@ -690,12 +749,18 @@ github.com/lyft/protoc-gen-star v0.6.1 h1:erE0rdztuaDq3bpGifD95wfoPrSZc95nGA6tbi github.com/lyft/protoc-gen-star/v2 v2.0.3 h1:/3+/2sWyXeMLzKd1bX+ixWKgEMsULrIivpDsuaF441o= github.com/lyft/protoc-gen-validate v0.0.13 h1:KNt/RhmQTOLr7Aj8PsJ7mTronaFyx80mRTT9qF261dA= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= +github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/matryer/moq v0.3.1 h1:kLDiBJoGcusWS2BixGyTkF224aSCD8nLY24tj/NcTCs= github.com/matryer/moq v0.3.1/go.mod h1:RJ75ZZZD71hejp39j4crZLsEDszGk6iH4v4YsWFKH4s= +github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= +github.com/mediocregopher/radix/v3 v3.8.1/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= +github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= +github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= @@ -716,11 +781,15 @@ github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8 h1:P48LjvUQpT github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 h1:dnMxwus89s86tI8rcGVp2HwZzlz7c5o92VOy7dSckBQ= github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU= github.com/nats-io/jwt/v2 v2.0.3 h1:i/O6cmIsjpcQyWDYNcq2JyZ3/VTF8SJ4JWluI5OhpvI= +github.com/nats-io/jwt/v2 v2.5.0/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI= github.com/nats-io/nats-server/v2 v2.5.0 h1:wsnVaaXH9VRSg+A2MVg5Q727/CqxnmPLGFQ3YZYKTQg= github.com/nats-io/nats.go v1.12.1 h1:+0ndxwUPz3CmQ2vjbXdkC1fo3FdiOQDim4gl3Mge8Qo= +github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc= github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= +github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/oapi-codegen/testutil v1.0.0/go.mod h1:ttCaYbHvJtHuiyeBF0tPIX+4uhEPTeizXKx28okijLw= github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= @@ -761,8 +830,12 @@ github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhA github.com/pact-foundation/pact-go v1.0.4 h1:OYkFijGHoZAYbOIb1LWXrwKQbMMRUv1oQ89blD2Mh2Q= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= +github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/performancecopilot/speed v3.0.0+incompatible h1:2WnRzIquHa5QxaJKShDkLM+sc0JPuwhXzK8OYOyt3Vg= github.com/performancecopilot/speed/v4 v4.0.0 h1:VxEDCmdkfbQYDlcr/GC9YoN9PQ6p8ulk9xVsepYy9ZY= +github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= @@ -798,6 +871,8 @@ github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= +github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e h1:uO75wNGioszjmIzcY/tvdDYKRLVvzggtAmmJkn9j4GQ= github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZFQ8e24NYaBGIlnO2WGCAi67re4HHuOm0sftE/M= github.com/segmentio/parquet-go v0.0.0-20230427215636-d483faba23a5 h1:7CWCjaHrXSUCHrRhIARMGDVKdB82tnPAQMmANeflKOw= @@ -808,6 +883,8 @@ github.com/sercand/kuberesolver/v5 v5.1.1 h1:CYH+d67G0sGBj7q5wLK61yzqJJ8gLLC8aep github.com/sercand/kuberesolver/v5 v5.1.1/go.mod h1:Fs1KbKhVRnB2aDWN12NjKCB+RgYMWZJ294T3BtmVCpQ= github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU= github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M= +github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.6 h1:Oe8TPH9wAbv++YPNDKJWUnI8Q4PPWCx3UbOfH+FxiMU= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= @@ -820,25 +897,28 @@ github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e h1:mOtuXaRAbVZsxAH github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/substrait-io/substrait-go v0.4.2 h1:buDnjsb3qAqTaNbOR7VKmNgXf4lYQxWEcnSGUWBtmN8= -github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tdewolff/minify/v2 v2.12.9 h1:dvn5MtmuQ/DFMwqf5j8QhEVpPX6fi3WGImhv8RUB4zA= +github.com/tdewolff/minify/v2 v2.12.9/go.mod h1:qOqdlDfL+7v0/fyymB+OP497nIxJYSvX4MQWA8OoiXU= +github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA= +github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o= github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/valyala/fasthttp v1.6.0 h1:uWF8lgKmeaIewWVPwi4GRq2P6+R46IgYZdxWtM+GtEY= github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c= github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/fasthttp v1.49.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vinzenz/yaml v0.0.0-20170920082545-91409cdd725d h1:3wDi6J5APMqaHBVPuVd7RmHD2gRTfqbdcVSpCNoUWtk= @@ -867,10 +947,13 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xhit/go-str2duration v1.2.0 h1:BcV5u025cITWxEQKGWr1URRzrcXtu7uk8+luz3Yuhwc= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= +github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= +github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= @@ -923,10 +1006,10 @@ go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= @@ -974,7 +1057,6 @@ gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6 h1:WN8Lymy+dCTDHgn4vhUSNIB6U+0sDiv/c9Zdr0UeAnI= k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6/go.mod h1:l0ukbPS0lwFxOzSq5ZqjutzF+5IL2TLp495PswRPSZk= -k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8= k8s.io/kms v0.29.0/go.mod h1:mB0f9HLxRXeXUfHfn1A7rpwOlzXI1gIWu86z6buNoYA= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 5712a3b683b..74ed02f5b1f 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -24,6 +24,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/stretchr/testify v1.9.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 3ae58b12bf5..f12745cd37b 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -43,8 +43,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 53b3eaa2ec0..48875edba5d 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -5,7 +5,7 @@ go 1.21.0 require ( github.com/bwmarrin/snowflake v0.3.0 github.com/gorilla/mux v1.8.1 - github.com/grafana/grafana-plugin-sdk-go v0.226.0 + github.com/grafana/grafana-plugin-sdk-go v0.227.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f github.com/stretchr/testify v1.9.0 golang.org/x/mod v0.15.0 @@ -40,7 +40,7 @@ require ( github.com/fatih/color v1.15.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/getkin/kin-openapi v0.120.0 // indirect + github.com/getkin/kin-openapi v0.124.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect @@ -79,7 +79,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 5a8bcf7c0e1..68c5a976c7b 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -64,8 +64,8 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= -github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= +github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -127,7 +127,7 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/grafana-plugin-sdk-go v0.226.0 h1:PDnxWbQDn9GXfp62MH604GZ73j0fsyxyrDhpm08N5vY= +github.com/grafana/grafana-plugin-sdk-go v0.227.0 h1:xkARhSnCovkcDd0n8uwingJID4fAn8tKX7nR2M22ML8= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f h1:+CK3tH3XrAAqx5urmVqpgSxMrL2MlpTOnLVSU4w4IjY= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f/go.mod h1:ZxIaCOlDmFupiL55aLU+Qp7O1dgwkDMBAQBK7wnEVBg= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -190,8 +190,7 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -242,8 +241,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/pkg/codegen/generators/go_generator.go b/pkg/codegen/generators/go_generator.go index a3744803dff..0f6d6b89bd0 100644 --- a/pkg/codegen/generators/go_generator.go +++ b/pkg/codegen/generators/go_generator.go @@ -12,7 +12,7 @@ import ( "cuelang.org/go/pkg/encoding/yaml" "github.com/dave/dst/decorator" "github.com/dave/dst/dstutil" - "github.com/deepmap/oapi-codegen/pkg/codegen" + "github.com/deepmap/oapi-codegen/v2/pkg/codegen" "github.com/getkin/kin-openapi/openapi3" "golang.org/x/tools/imports" ) @@ -182,8 +182,8 @@ import ( "strings" "time" - "github.com/deepmap/oapi-codegen/pkg/runtime" - openapi_types "github.com/deepmap/oapi-codegen/pkg/types" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" "github.com/getkin/kin-openapi/openapi3" "github.com/go-chi/chi/v5" "github.com/labstack/echo/v4" diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go index 5394eb48e94..8c94c29ef29 100644 --- a/pkg/extensions/main.go +++ b/pkg/extensions/main.go @@ -29,6 +29,7 @@ import ( _ "github.com/stretchr/testify/require" _ "github.com/vectordotdev/go-datemath" _ "golang.org/x/time/rate" + _ "xorm.io/builder" ) var IsEnterprise bool = false diff --git a/pkg/plugins/backendplugin/grpcplugin/log_wrapper.go b/pkg/plugins/backendplugin/grpcplugin/log_wrapper.go index 38052b9bc1d..25b975f0c58 100644 --- a/pkg/plugins/backendplugin/grpcplugin/log_wrapper.go +++ b/pkg/plugins/backendplugin/grpcplugin/log_wrapper.go @@ -149,6 +149,11 @@ func (lw logWrapper) ResetNamed(name string) hclog.Logger { } } +// No-op. The wrapped logger implementation cannot update the level on the fly. +func (lw logWrapper) GetLevel() hclog.Level { + return hclog.Trace +} + // No-op. The wrapped logger implementation cannot update the level on the fly. func (lw logWrapper) SetLevel(level hclog.Level) {} diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 9f89fcbf839..bde32825bee 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/promlib go 1.21.0 require ( - github.com/grafana/grafana-plugin-sdk-go v0.226.0 + github.com/grafana/grafana-plugin-sdk-go v0.227.0 github.com/json-iterator/go v1.1.12 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.19.0 @@ -35,7 +35,7 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/getkin/kin-openapi v0.120.0 // indirect + github.com/getkin/kin-openapi v0.124.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.1 // indirect @@ -68,7 +68,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 30f4d0b2611..b0967562ab3 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -48,8 +48,7 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= -github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= @@ -90,7 +89,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/grafana-plugin-sdk-go v0.226.0 h1:PDnxWbQDn9GXfp62MH604GZ73j0fsyxyrDhpm08N5vY= +github.com/grafana/grafana-plugin-sdk-go v0.227.0 h1:xkARhSnCovkcDd0n8uwingJID4fAn8tKX7nR2M22ML8= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= @@ -143,8 +142,7 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -190,8 +188,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/pkg/util/xorm/go.mod b/pkg/util/xorm/go.mod index d499a5d5137..88a83bc208a 100644 --- a/pkg/util/xorm/go.mod +++ b/pkg/util/xorm/go.mod @@ -14,7 +14,7 @@ require ( github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/util/xorm/go.sum b/pkg/util/xorm/go.sum index 15e6719de9d..6a1b138bd40 100644 --- a/pkg/util/xorm/go.sum +++ b/pkg/util/xorm/go.sum @@ -23,8 +23,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= From 0f98bd3b7bed7687fe07df88fe55f986d4302daa Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:18:02 +0100 Subject: [PATCH 123/222] Chore: Rewrite dashboard component css using object styles (#86930) --- .betterer.results | 170 ++---------------- .../AddWidgetModal/AddWidgetModal.tsx | 29 ++- .../AnnotationSettingsEdit.tsx | 6 +- .../AnnotationSettingsList.tsx | 8 +- .../components/DashNav/DashNavButton.tsx | 8 +- .../DashboardLoading/DashboardFailed.tsx | 12 +- .../DashboardLoading/DashboardLoading.tsx | 22 +-- .../DashboardSettings/ListNewButton.tsx | 6 +- .../DeleteDashboard/DeleteDashboardModal.tsx | 6 +- .../components/HelpWizard/HelpWizard.tsx | 38 ++-- .../PanelEditor/DynamicConfigValueEditor.tsx | 26 ++- .../components/PanelEditor/OptionsPane.tsx | 72 ++++---- .../PanelEditor/OptionsPaneItemDescriptor.tsx | 8 +- .../PanelEditor/OptionsPaneOptions.tsx | 100 +++++------ .../PanelEditor/OverrideCategoryTitle.tsx | 36 ++-- .../components/PanelEditor/PanelEditor.tsx | 116 ++++++------ .../PanelEditor/PanelEditorTabs.tsx | 40 ++--- .../PanelEditor/VisualizationButton.tsx | 14 +- .../PanelEditor/VisualizationSelectPane.tsx | 82 ++++----- .../PublicDashboardNotAvailable.tsx | 58 +++--- .../components/RowOptions/RowOptionsModal.tsx | 8 +- .../SaveDashboard/SaveDashboardErrorProxy.tsx | 31 ++-- .../SaveDashboard/UnsavedChangesModal.tsx | 6 +- .../SaveDashboard/forms/SaveDashboardForm.tsx | 12 +- .../forms/SaveProvisionedDashboardForm.tsx | 14 +- .../ConfigPublicDashboard.tsx | 30 ++-- .../EmailSharingConfiguration.tsx | 92 +++++----- .../ConfigPublicDashboard/SettingsSummary.tsx | 12 +- .../AcknowledgeCheckboxes.tsx | 6 +- .../CreatePublicDashboard.tsx | 46 ++--- .../UnsupportedDataSourcesAlert.tsx | 8 +- .../components/SubMenu/AnnotationPicker.tsx | 32 ++-- .../dashboard/components/SubMenu/SubMenu.tsx | 27 ++- .../TransformationEditor.tsx | 143 ++++++++------- .../TransformationPickerNg.tsx | 20 +-- 35 files changed, 593 insertions(+), 751 deletions(-) diff --git a/.betterer.results b/.betterer.results index ac9529357cb..acb169c5f64 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2495,17 +2495,8 @@ exports[`better eslint`] = { "public/app/features/dashboard/components/AddLibraryPanelWidget/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./AddLibraryPanelWidget\`)", "0"] ], - "public/app/features/dashboard/components/AddWidgetModal/AddWidgetModal.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] - ], "public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], - "public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], "public/app/features/dashboard/components/AnnotationSettings/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./AnnotationSettingsEdit\`)", "0"], @@ -2534,20 +2525,12 @@ exports[`better eslint`] = { "public/app/features/dashboard/components/DashExportModal/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./DashboardExporter\`)", "0"] ], - "public/app/features/dashboard/components/DashNav/DashNavButton.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], "public/app/features/dashboard/components/DashNav/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`DashNav\`)", "0"] ], - "public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], "public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"] + [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "1"] ], "public/app/features/dashboard/components/DashboardPrompt/DashboardPrompt.test.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -2569,23 +2552,14 @@ exports[`better eslint`] = { "public/app/features/dashboard/components/DashboardRow/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./DashboardRow\`)", "0"] ], - "public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], "public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], "public/app/features/dashboard/components/DashboardSettings/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./DashboardSettings\`)", "0"] ], - "public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], "public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"] + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], "public/app/features/dashboard/components/Inspector/PanelInspector.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] @@ -2595,81 +2569,26 @@ exports[`better eslint`] = { [0, 0, 0, "Do not re-export imported variable (\`./LinkSettingsList\`)", "1"] ], "public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] - ], - "public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"] + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], "public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] - ], - "public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"], - [0, 0, 0, "Styles should be written using objects.", "8"], - [0, 0, 0, "Styles should be written using objects.", "9"] + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] ], "public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"] + [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"], - [0, 0, 0, "Styles should be written using objects.", "8"], - [0, 0, 0, "Styles should be written using objects.", "9"], - [0, 0, 0, "Styles should be written using objects.", "10"], - [0, 0, 0, "Styles should be written using objects.", "11"] - ], - "public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] + [0, 0, 0, "Do not use any type assertions.", "1"] ], "public/app/features/dashboard/components/PanelEditor/PanelNotSupported.tsx:5381": [ [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], [0, 0, 0, "\'Layout\' import from \'@grafana/ui/src/components/Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "1"] ], - "public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], "public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx:5381": [ - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"], - [0, 0, 0, "Styles should be written using objects.", "8"], - [0, 0, 0, "Styles should be written using objects.", "9"], - [0, 0, 0, "Styles should be written using objects.", "10"] + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"] ], "public/app/features/dashboard/components/PanelEditor/getFieldOverrideElements.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -2687,27 +2606,10 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "4"], [0, 0, 0, "Unexpected any. Specify a different type.", "5"] ], - "public/app/features/dashboard/components/PublicDashboardNotAvailable/PublicDashboardNotAvailable.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"] - ], - "public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], "public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx:5381": [ [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"], [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"] ], - "public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] - ], - "public/app/features/dashboard/components/SaveDashboard/UnsavedChangesModal.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], "public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.test.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -2715,12 +2617,10 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "1"], [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"] + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"] ], "public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], "public/app/features/dashboard/components/SaveDashboard/types.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] @@ -2733,70 +2633,26 @@ exports[`better eslint`] = { ], "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "\'Layout\' import from \'@grafana/ui/src/components/Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"] + [0, 0, 0, "\'Layout\' import from \'@grafana/ui/src/components/Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "1"] ], "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/Configuration.tsx:5381": [ [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "0"], [0, 0, 0, "\'Layout\' import from \'@grafana/ui/src/components/Layout/Layout\' is restricted from being used by a pattern. Use Stack component instead.", "1"] ], - "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/EmailSharingConfiguration.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"] - ], - "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsSummary.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] - ], "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] - ], - "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/CreatePublicDashboard.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"] - ], - "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"] + [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "1"] ], "public/app/features/dashboard/components/ShareModal/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./ShareModal\`)", "0"], [0, 0, 0, "Do not use export all (\`export * from ...\`)", "1"] ], - "public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], "public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], - "public/app/features/dashboard/components/SubMenu/SubMenu.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], "public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"], - [0, 0, 0, "Styles should be written using objects.", "3"], - [0, 0, 0, "Styles should be written using objects.", "4"], - [0, 0, 0, "Styles should be written using objects.", "5"], - [0, 0, 0, "Styles should be written using objects.", "6"], - [0, 0, 0, "Styles should be written using objects.", "7"], - [0, 0, 0, "Styles should be written using objects.", "8"], - [0, 0, 0, "Styles should be written using objects.", "9"], - [0, 0, 0, "Styles should be written using objects.", "10"] + [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx:5381": [ [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] diff --git a/public/app/features/dashboard/components/AddWidgetModal/AddWidgetModal.tsx b/public/app/features/dashboard/components/AddWidgetModal/AddWidgetModal.tsx index fc7cbc81780..2dc2e392dbe 100644 --- a/public/app/features/dashboard/components/AddWidgetModal/AddWidgetModal.tsx +++ b/public/app/features/dashboard/components/AddWidgetModal/AddWidgetModal.tsx @@ -68,19 +68,18 @@ export const AddWidgetModal = () => { }; const getStyles = (theme: GrafanaTheme2) => ({ - modal: css` - width: 65%; - max-width: 960px; - - ${theme.breakpoints.down('md')} { - width: 100%; - } - `, - searchInput: css` - margin-bottom: ${theme.spacing(2)}; - `, - grid: css` - display: grid; - grid-gap: ${theme.spacing(1)}; - `, + modal: css({ + width: '65%', + maxWidth: '960px', + [theme.breakpoints.down('md')]: { + width: '100%', + }, + }), + searchInput: css({ + marginBottom: theme.spacing(2), + }), + grid: css({ + display: 'grid', + gridGap: theme.spacing(1), + }), }); diff --git a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx index 5ebc23acdd3..acd171bb47b 100644 --- a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx +++ b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsEdit.tsx @@ -265,9 +265,9 @@ const getStyles = (theme: GrafanaTheme2) => { maxWidth: theme.spacing(60), marginBottom: theme.spacing(2), }), - select: css` - margin-top: 8px; - `, + select: css({ + marginTop: '8px', + }), }; }; diff --git a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx index 6a5579ee843..937fb188daa 100644 --- a/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx +++ b/public/app/features/dashboard/components/AnnotationSettings/AnnotationSettingsList.tsx @@ -154,8 +154,8 @@ export const AnnotationSettingsList = ({ dashboard, onNew, onEdit }: Props) => { }; const getStyles = () => ({ - table: css` - width: 100%; - overflow-x: scroll; - `, + table: css({ + width: '100%', + overflowX: 'scroll', + }), }); diff --git a/public/app/features/dashboard/components/DashNav/DashNavButton.tsx b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx index 86e48b6a173..df71687f1b7 100644 --- a/public/app/features/dashboard/components/DashNav/DashNavButton.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx @@ -37,8 +37,8 @@ export const DashNavButton = ({ icon, iconType, iconSize, tooltip, onClick, chil }; const getStyles = (theme: GrafanaTheme2) => ({ - noBorderContainer: css` - padding: 0 ${theme.spacing(0.5)}; - display: flex; - `, + noBorderContainer: css({ + padding: `0 ${theme.spacing(0.5)}`, + display: 'flex', + }), }); diff --git a/public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx b/public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx index 8d9cde4f8b6..7cbc204729b 100644 --- a/public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx +++ b/public/app/features/dashboard/components/DashboardLoading/DashboardFailed.tsx @@ -24,10 +24,10 @@ export const DashboardFailed = ({ initError }: Props) => { }; export const styles = { - dashboardLoading: css` - height: 60vh; - display: flex; - align-items: center; - justify-content: center; - `, + dashboardLoading: css({ + height: '60vh', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }), }; diff --git a/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx b/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx index 2d71a85ccc0..a93406e4a39 100644 --- a/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx +++ b/public/app/features/dashboard/components/DashboardLoading/DashboardLoading.tsx @@ -44,16 +44,16 @@ export const getStyles = (theme: GrafanaTheme2) => { `; return { - dashboardLoading: css` - height: 60vh; - display: flex; - opacity: 0%; - align-items: center; - justify-content: center; - animation: ${invisibleToVisible} 0s step-end ${slowStartThreshold} 1 normal forwards; - `, - dashboardLoadingText: css` - font-size: ${theme.typography.h4.fontSize}; - `, + dashboardLoading: css({ + height: '60vh', + display: 'flex', + opacity: '0%', + alignItems: 'center', + justifyContent: 'center', + animation: `${invisibleToVisible} 0s step-end ${slowStartThreshold} 1 normal forwards`, + }), + dashboardLoadingText: css({ + fontSize: theme.typography.h4.fontSize, + }), }; }; diff --git a/public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx b/public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx index eafdcb04e2e..582a9fcc2f0 100644 --- a/public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/ListNewButton.tsx @@ -18,7 +18,7 @@ export const ListNewButton = ({ children, ...restProps }: Props) => { }; const getStyles = (theme: GrafanaTheme2) => ({ - buttonWrapper: css` - padding: ${theme.spacing(3)} 0; - `, + buttonWrapper: css({ + padding: `${theme.spacing(3)} 0`, + }), }); diff --git a/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx b/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx index 1b9fb0088d4..0cd44c3ccb2 100644 --- a/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx +++ b/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx @@ -60,9 +60,9 @@ const ProvisionedDeleteModal = ({ hideModal, provisionedId }: { hideModal(): voi title="Cannot delete provisioned dashboard" icon="trash-alt" onDismiss={hideModal} - className={css` - width: 500px; - `} + className={css({ + width: '500px', + })} >

This dashboard is managed by Grafana provisioning and cannot be deleted. Remove the dashboard from the config file diff --git a/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx b/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx index cb94d8c67b4..ed2176b1644 100644 --- a/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx +++ b/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx @@ -212,24 +212,22 @@ export function HelpWizard({ panel, plugin, onClose }: Props) { } const getStyles = (theme: GrafanaTheme2) => ({ - code: css` - flex-grow: 1; - height: 100%; - overflow: scroll; - `, - field: css` - width: 100%; - `, - opts: css` - display: flex; - display: flex; - width: 100%; - flex-grow: 0; - align-items: center; - justify-content: flex-end; - - button { - margin-left: 8px; - } - `, + code: css({ + flexGrow: 1, + height: '100%', + overflow: 'scroll', + }), + field: css({ + width: '100%', + }), + opts: css({ + display: 'flex', + width: '100%', + flexGrow: 0, + alignItems: 'center', + justifyContent: 'flex-end', + button: { + marginLeft: '8px', + }, + }), }); diff --git a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx index bd31fb439a8..e6dbd47bf01 100644 --- a/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/DynamicConfigValueEditor.tsx @@ -83,10 +83,10 @@ export const DynamicConfigValueEditor = ({ @@ -130,13 +130,11 @@ export const DynamicConfigValueEditor = ({ ); }; -const getStyles = (theme: GrafanaTheme2) => { - return { - collapsibleOverrideEditor: css` - label: collapsibleOverrideEditor; - & + .dynamicConfigValueEditor--nonCollapsible { - margin-top: ${theme.spacing(1)}; - } - `, - }; -}; +const getStyles = (theme: GrafanaTheme2) => ({ + collapsibleOverrideEditor: css({ + label: 'collapsibleOverrideEditor', + '& + .dynamicConfigValueEditor--nonCollapsible': { + marginTop: theme.spacing(1), + }, + }), +}); diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx index 099ef61d065..32a7d658271 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPane.tsx @@ -53,42 +53,40 @@ export const OptionsPane = ({ const getStyles = (theme: GrafanaTheme2) => { return { - wrapper: css` - height: 100%; - width: 100%; - display: flex; - flex: 1 1 0; - flex-direction: column; - padding: 0; - `, - optionsWrapper: css` - flex-grow: 1; - min-height: 0; - `, - vizButtonWrapper: css` - padding: 0 ${theme.spacing(2, 2)} 0; - `, - legacyOptions: css` - label: legacy-options; - .panel-options-grid { - display: flex; - flex-direction: column; - } - .panel-options-group { - margin-bottom: 0; - } - .panel-options-group__body { - padding: ${theme.spacing(2)} 0; - } - - .section { - display: block; - margin: ${theme.spacing(2)} 0; - - &:first-child { - margin-top: 0; - } - } - `, + wrapper: css({ + height: '100%', + width: '100%', + display: 'flex', + flex: '1 1 0', + flexDirection: 'column', + padding: 0, + }), + optionsWrapper: css({ + flexGrow: 1, + minHeight: 0, + }), + vizButtonWrapper: css({ + padding: `0 ${theme.spacing(2, 2)} 0`, + }), + legacyOptions: css({ + label: 'legacy-options', + '.panel-options-grid': { + display: 'flex', + flexDirection: 'column', + }, + '.panel-options-group': { + marginBottom: 0, + }, + '.panel-options-group__body': { + padding: `${theme.spacing(2)} 0`, + }, + '.section': { + display: 'block', + margin: `${theme.spacing(2)} 0`, + '&:first-child': { + marginTop: 0, + }, + }, + }), }; }; diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx index 125bb6fc8d9..c8b67b5e8ce 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx @@ -120,9 +120,9 @@ function OptionPaneLabel({ title, description, overrides, addon }: OptionPanelLa function getLabelStyles(theme: GrafanaTheme2) { return { - container: css` - display: flex; - justify-content: space-between; - `, + container: css({ + display: 'flex', + justifyContent: 'space-between', + }), }; } diff --git a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx index db9039bb24d..c27378cb556 100644 --- a/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.tsx @@ -174,55 +174,55 @@ export function renderSearchHits( } const getStyles = (theme: GrafanaTheme2) => ({ - wrapper: css` - height: 100%; - display: flex; - flex-direction: column; - flex: 1 1 0; + wrapper: css({ + height: '100%', + display: 'flex', + flexDirection: 'column', + flex: '1 1 0', - .search-fragment-highlight { - color: ${theme.colors.warning.text}; - background: transparent; - } - `, - searchBox: css` - display: flex; - flex-direction: column; - min-height: 0; - `, - formRow: css` - margin-bottom: ${theme.spacing(1)}; - `, - formBox: css` - padding: ${theme.spacing(1)}; - background: ${theme.colors.background.primary}; - border: 1px solid ${theme.components.panel.borderColor}; - border-top-left-radius: ${theme.shape.borderRadius(1.5)}; - border-bottom: none; - `, - closeButton: css` - margin-left: ${theme.spacing(1)}; - `, - searchHits: css` - padding: ${theme.spacing(1, 1, 0, 1)}; - `, - scrollWrapper: css` - flex-grow: 1; - min-height: 0; - `, - searchNotice: css` - font-size: ${theme.typography.size.sm}; - color: ${theme.colors.text.secondary}; - padding: ${theme.spacing(1)}; - text-align: center; - `, - mainBox: css` - background: ${theme.colors.background.primary}; - border: 1px solid ${theme.components.panel.borderColor}; - border-top: none; - flex-grow: 1; - `, - angularDeprecationWrapper: css` - padding: ${theme.spacing(1)}; - `, + '.search-fragment-highlight': { + color: theme.colors.warning.text, + background: 'transparent', + }, + }), + searchBox: css({ + display: 'flex', + flexDirection: 'column', + minHeight: 0, + }), + formRow: css({ + marginBottom: theme.spacing(1), + }), + formBox: css({ + padding: theme.spacing(1), + background: theme.colors.background.primary, + border: `1px solid ${theme.components.panel.borderColor}`, + borderTopLeftRadius: theme.shape.borderRadius(1.5), + borderBottom: 'none', + }), + closeButton: css({ + marginLeft: theme.spacing(1), + }), + searchHits: css({ + padding: theme.spacing(1, 1, 0, 1), + }), + scrollWrapper: css({ + flexGrow: 1, + minHeight: 0, + }), + searchNotice: css({ + fontSize: theme.typography.size.sm, + color: theme.colors.text.secondary, + padding: theme.spacing(1), + textAlign: 'center', + }), + mainBox: css({ + background: theme.colors.background.primary, + border: `1px solid ${theme.components.panel.borderColor}`, + borderTop: 'none', + flexGrow: 1, + }), + angularDeprecationWrapper: css({ + padding: theme.spacing(1), + }), }); diff --git a/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx b/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx index 27e5c9768ec..1474507224b 100644 --- a/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx +++ b/public/app/features/dashboard/components/PanelEditor/OverrideCategoryTitle.tsx @@ -47,23 +47,23 @@ OverrideCategoryTitle.displayName = 'OverrideTitle'; const getStyles = (theme: GrafanaTheme2) => { return { - matcherUi: css` - padding: ${theme.spacing(1)}; - `, - propertyPickerWrapper: css` - margin-top: ${theme.spacing(2)}; - `, - overrideDetails: css` - font-size: ${theme.typography.bodySmall.fontSize}; - color: ${theme.colors.text.secondary}; - font-weight: ${theme.typography.fontWeightRegular}; - `, - options: css` - overflow: hidden; - padding-right: ${theme.spacing(4)}; - `, - unknownLabel: css` - margin-bottom: 0; - `, + matcherUi: css({ + padding: theme.spacing(1), + }), + propertyPickerWrapper: css({ + marginTop: theme.spacing(2), + }), + overrideDetails: css({ + fontSize: theme.typography.bodySmall.fontSize, + color: theme.colors.text.secondary, + fontWeight: theme.typography.fontWeightRegular, + }), + options: css({ + overflow: 'hidden', + paddingRight: theme.spacing(4), + }), + unknownLabel: css({ + marginBottom: 0, + }), }; }; diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx index 7360ac615cc..ee32162851c 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx @@ -502,64 +502,64 @@ export const getStyles = stylesFactory((theme: GrafanaTheme2, props: Props) => { display: 'flex', paddingTop: theme.spacing(2), }), - verticalSplitPanesWrapper: css` - display: flex; - flex-direction: column; - height: 100%; - width: 100%; - position: relative; - `, - mainPaneWrapper: css` - display: flex; - flex-direction: column; - height: 100%; - width: 100%; - padding-right: ${uiState.isPanelOptionsVisible ? 0 : paneSpacing}; - `, - variablesWrapper: css` - label: variablesWrapper; - display: flex; - flex-grow: 1; - flex-wrap: wrap; - gap: ${theme.spacing(1, 2)}; - `, - panelWrapper: css` - flex: 1 1 0; - min-height: 0; - width: 100%; - padding-left: ${paneSpacing}; - `, - tabsWrapper: css` - height: 100%; - width: 100%; - `, - panelToolbar: css` - display: flex; - padding: 0 0 ${paneSpacing} ${paneSpacing}; - justify-content: space-between; - flex-wrap: wrap; - `, - angularWarning: css` - display: flex; - height: theme.spacing(4); - align-items: center; - `, - toolbarLeft: css` - padding-left: ${theme.spacing(1)}; - `, - centeringContainer: css` - display: flex; - justify-content: center; - align-items: center; - position: relative; - flex-direction: column; - `, - onlyPanel: css` - height: 100%; - position: absolute; - overflow: hidden; - width: 100%; - `, + verticalSplitPanesWrapper: css({ + display: 'flex', + flexDirection: 'column', + height: '100%', + width: '100%', + position: 'relative', + }), + mainPaneWrapper: css({ + display: 'flex', + flexDirection: 'column', + height: '100%', + width: '100%', + paddingRight: `${uiState.isPanelOptionsVisible ? 0 : paneSpacing}`, + }), + variablesWrapper: css({ + label: 'variablesWrapper', + display: 'flex', + flexGrow: 1, + flexWrap: 'wrap', + gap: theme.spacing(1, 2), + }), + panelWrapper: css({ + flex: '1 1 0', + minHeight: 0, + width: '100%', + paddingLeft: paneSpacing, + }), + tabsWrapper: css({ + height: '100%', + width: '100%', + }), + panelToolbar: css({ + display: 'flex', + padding: `0 0 ${paneSpacing} ${paneSpacing}`, + justifyContent: 'space-between', + flexWrap: 'wrap', + }), + angularWarning: css({ + display: 'flex', + height: theme.spacing(4), + alignItems: 'center', + }), + toolbarLeft: css({ + paddingLeft: theme.spacing(1), + }), + centeringContainer: css({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + position: 'relative', + flexDirection: 'column', + }), + onlyPanel: css({ + height: '100%', + position: 'absolute', + overflow: 'hidden', + width: '100%', + }), }; }); diff --git a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx index a60f5bd5483..5c7769d7654 100644 --- a/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx +++ b/public/app/features/dashboard/components/PanelEditor/PanelEditorTabs.tsx @@ -113,25 +113,25 @@ function getCounter(panel: PanelModel, tab: PanelEditorTab) { const getStyles = (theme: GrafanaTheme2) => { return { - wrapper: css` - display: flex; - flex-direction: column; - height: 100%; - `, - tabBar: css` - padding-left: ${theme.spacing(2)}; - `, - tabContent: css` - padding: 0; - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; - background: ${theme.colors.background.primary}; - border: 1px solid ${theme.components.panel.borderColor}; - border-left: none; - border-bottom: none; - border-top-right-radius: ${theme.shape.borderRadius(1.5)}; - `, + wrapper: css({ + display: 'flex', + flexDirection: 'column', + height: '100%', + }), + tabBar: css({ + paddingLeft: theme.spacing(2), + }), + tabContent: css({ + padding: 0, + display: 'flex', + flexDirection: 'column', + flex: 1, + minHeight: 0, + background: theme.colors.background.primary, + border: `1px solid ${theme.components.panel.borderColor}`, + borderLeft: 'none', + borderBottom: 'none', + borderTopRightRadius: theme.shape.borderRadius(1.5), + }), }; }; diff --git a/public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx b/public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx index 9a89428415a..87c8453bafb 100644 --- a/public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx +++ b/public/app/features/dashboard/components/PanelEditor/VisualizationButton.tsx @@ -65,11 +65,11 @@ export const VisualizationButton = ({ panel }: Props) => { VisualizationButton.displayName = 'VisualizationTab'; const styles = { - wrapper: css` - display: flex; - flex-direction: column; - `, - vizButton: css` - text-align: left; - `, + wrapper: css({ + display: 'flex', + flexDirection: 'column', + }), + vizButton: css({ + textAlign: 'left', + }), }; diff --git a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx index 972e6f82edb..372734eb211 100644 --- a/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx +++ b/public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx @@ -143,46 +143,46 @@ VisualizationSelectPane.displayName = 'VisualizationSelectPane'; const getStyles = (theme: GrafanaTheme2) => { return { - icon: css` - color: ${theme.v1.palette.gray33}; - `, - wrapper: css` - display: flex; - flex-direction: column; - flex: 1 1 0; - height: 100%; - `, - vizButton: css` - text-align: left; - `, - scrollWrapper: css` - flex-grow: 1; - min-height: 0; - `, - scrollContent: css` - padding: ${theme.spacing(1)}; - `, - openWrapper: css` - display: flex; - flex-direction: column; - flex: 1 1 100%; - height: 100%; - background: ${theme.colors.background.primary}; - border: 1px solid ${theme.colors.border.weak}; - `, - searchRow: css` - display: flex; - margin-bottom: ${theme.spacing(1)}; - `, - closeButton: css` - margin-left: ${theme.spacing(1)}; - `, - customFieldMargin: css` - margin-bottom: ${theme.spacing(1)}; - `, - formBox: css` - padding: ${theme.spacing(1)}; - padding-bottom: 0; - `, + icon: css({ + color: theme.v1.palette.gray33, + }), + wrapper: css({ + display: 'flex', + flexDirection: 'column', + flex: '1 1 0', + height: '100%', + }), + vizButton: css({ + textAlign: 'left', + }), + scrollWrapper: css({ + flexGrow: 1, + minHeight: 0, + }), + scrollContent: css({ + padding: theme.spacing(1), + }), + openWrapper: css({ + display: 'flex', + flexDirection: 'column', + flex: '1 1 100%', + height: '100%', + background: theme.colors.background.primary, + border: `1px solid ${theme.colors.border.weak}`, + }), + searchRow: css({ + display: 'flex', + marginBottom: theme.spacing(1), + }), + closeButton: css({ + marginLeft: theme.spacing(1), + }), + customFieldMargin: css({ + marginBottom: theme.spacing(1), + }), + formBox: css({ + padding: theme.spacing(1), + paddingBottom: 0, + }), }; }; diff --git a/public/app/features/dashboard/components/PublicDashboardNotAvailable/PublicDashboardNotAvailable.tsx b/public/app/features/dashboard/components/PublicDashboardNotAvailable/PublicDashboardNotAvailable.tsx index 9e493da1eb7..6e4bf939f95 100644 --- a/public/app/features/dashboard/components/PublicDashboardNotAvailable/PublicDashboardNotAvailable.tsx +++ b/public/app/features/dashboard/components/PublicDashboardNotAvailable/PublicDashboardNotAvailable.tsx @@ -35,34 +35,34 @@ export const PublicDashboardNotAvailable = ({ paused }: { paused?: boolean }) => }; const getStyles = (theme: GrafanaTheme2) => ({ - container: css` - display: flex; - justify-content: center; - align-items: center; - height: 100%; + container: css({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100%', - :before { - opacity: 1; - } - `, - box: css` - width: 608px; - display: flex; - align-items: center; - flex-direction: column; - gap: ${theme.spacing(4)}; - z-index: 1; - border-radius: ${theme.shape.borderRadius(4)}; - padding: ${theme.spacing(6, 8)}; - opacity: 1; - `, - title: css` - font-size: ${theme.typography.h3.fontSize}; - text-align: center; - margin: 0; - `, - description: css` - font-size: ${theme.typography.h5.fontSize}; - margin: 0; - `, + ':before': { + opacity: 1, + }, + }), + box: css({ + width: '608px', + display: 'flex', + alignItems: 'center', + flexDirection: 'column', + gap: theme.spacing(4), + zIndex: 1, + borderRadius: theme.shape.borderRadius(4), + padding: theme.spacing(6, 8), + opacity: 1, + }), + title: css({ + fontSize: theme.typography.h3.fontSize, + textAlign: 'center', + margin: 0, + }), + description: css({ + fontSize: theme.typography.h5.fontSize, + margin: 0, + }), }); diff --git a/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx b/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx index d4c3979663f..68a7070551a 100644 --- a/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx +++ b/public/app/features/dashboard/components/RowOptions/RowOptionsModal.tsx @@ -24,8 +24,8 @@ export const RowOptionsModal = ({ repeat, title, onDismiss, onUpdate, warning }: }; const getStyles = () => ({ - modal: css` - label: RowOptionsModal; - width: 500px; - `, + modal: css({ + label: 'RowOptionsModal', + width: '500px', + }), }); diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx index 60d5533e153..0049a8a2caf 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx @@ -126,20 +126,19 @@ export const proxyHandlesError = (errorStatus: string) => { }; const getConfirmPluginDashboardSaveModalStyles = (theme: GrafanaTheme2) => ({ - modal: css` - width: 500px; - `, - modalText: css` - font-size: ${theme.typography.h4.fontSize}; - color: ${theme.colors.text.primary}; - margin-bottom: ${theme.spacing(4)} - padding-top: ${theme.spacing(2)}; - `, - modalButtonRow: css` - margin-bottom: 14px; - a, - button { - margin-right: ${theme.spacing(2)}; - } - `, + modal: css({ + width: '500px', + }), + modalText: css({ + fontSize: theme.typography.h4.fontSize, + color: theme.colors.text.primary, + marginBottom: theme.spacing(4), + paddingTop: theme.spacing(2), + }), + modalButtonRow: css({ + marginBottom: '14px', + 'a, button': { + marginRight: theme.spacing(2), + }, + }), }); diff --git a/public/app/features/dashboard/components/SaveDashboard/UnsavedChangesModal.tsx b/public/app/features/dashboard/components/SaveDashboard/UnsavedChangesModal.tsx index 42f0dc9bdd0..5fe3bdfe1cf 100644 --- a/public/app/features/dashboard/components/SaveDashboard/UnsavedChangesModal.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/UnsavedChangesModal.tsx @@ -21,9 +21,9 @@ export const UnsavedChangesModal = ({ dashboard, onSaveSuccess, onDiscard, onDis title="Unsaved changes" onDismiss={onDismiss} icon="exclamation-triangle" - className={css` - width: 500px; - `} + className={css({ + width: '500px', + })} >

Do you want to save your changes?
diff --git a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx index d742c75550e..492863b5c9a 100644 --- a/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx @@ -135,11 +135,11 @@ export const SaveDashboardForm = ({ function getStyles(theme: GrafanaTheme2) { return { - message: css` - display: flex; - align-items: end; - flex-direction: column; - width: 100%; - `, + message: css({ + display: 'flex', + alignItems: 'end', + flexDirection: 'column', + width: '100%', + }), }; } diff --git a/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx b/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx index 785b568528c..d8c3cb4b720 100644 --- a/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx @@ -67,11 +67,11 @@ export const SaveProvisionedDashboardForm = ({ dashboard, onCancel }: Omit @@ -265,18 +265,18 @@ export function ConfigPublicDashboard({ publicDashboard, unsupportedDatasources } const getStyles = (theme: GrafanaTheme2) => ({ - configContainer: css` - label: config container; - display: flex; - flex-direction: column; - flex-wrap: wrap; - gap: ${theme.spacing(3)}; - `, - fieldSpace: css` - label: field space; - width: 100%; - margin-bottom: 0; - `, + configContainer: css({ + label: 'config container', + display: 'flex', + flexDirection: 'column', + flexWrap: 'wrap', + gap: theme.spacing(3), + }), + fieldSpace: css({ + label: 'field space', + width: '100%', + marginBottom: 0, + }), timeRange: css({ display: 'inline-block', }), diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/EmailSharingConfiguration.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/EmailSharingConfiguration.tsx index 891531b4eda..95d2280c41b 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/EmailSharingConfiguration.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/EmailSharingConfiguration.tsx @@ -228,52 +228,50 @@ export const EmailSharingConfiguration = ({ dashboard }: { dashboard: DashboardM }; const getStyles = (theme: GrafanaTheme2) => ({ - container: css` - label: emailConfigContainer; - display: flex; - flex-direction: column; - flex-wrap: wrap; - gap: ${theme.spacing(3)}; - `, - field: css` - label: field-noMargin; - margin-bottom: 0; - `, - emailContainer: css` - label: emailContainer; - display: flex; - gap: ${theme.spacing(1)}; - `, - emailInput: css` - label: emailInput; - flex-grow: 1; - `, - table: css` - label: table; - display: flex; - max-height: 220px; - overflow-y: scroll; + container: css({ + label: 'emailConfigContainer', + display: 'flex', + flexDirection: 'column', + flexWrap: 'wrap', + gap: theme.spacing(3), + }), + field: css({ + label: 'field-noMargin', + marginBottom: 0, + }), + emailContainer: css({ + label: 'emailContainer', + display: 'flex', + gap: theme.spacing(1), + }), + emailInput: css({ + label: 'emailInput', + flexGrow: 1, + }), + table: css({ + label: 'table', + display: 'flex', + maxHeight: '220px', + overflowY: 'scroll', + '& tbody': { + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + }, + '& tr': { + minHeight: '40px', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: theme.spacing(0.5, 1), - & tbody { - display: flex; - flex-direction: column; - flex-grow: 1; - } - - & tr { - min-height: 40px; - display: flex; - align-items: center; - justify-content: space-between; - padding: ${theme.spacing(0.5, 1)}; - - :nth-child(odd) { - background: ${theme.colors.background.secondary}; - } - } - `, - tableButtonsContainer: css` - display: flex; - justify-content: end; - `, + ':nth-child(odd)': { + background: theme.colors.background.secondary, + }, + }, + }), + tableButtonsContainer: css({ + display: 'flex', + justifyContent: 'end', + }), }); diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsSummary.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsSummary.tsx index f8fb436d042..d907ba78bb4 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsSummary.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/SettingsSummary.tsx @@ -66,12 +66,12 @@ const getStyles = (theme: GrafanaTheme2) => { summaryWrapper: css({ display: 'flex', }), - summary: css` - label: collapsedText; - margin-left: ${theme.spacing.gridSize * 2}px; - font-size: ${theme.typography.bodySmall.fontSize}; - color: ${theme.colors.text.secondary}; - `, + summary: css({ + label: 'collapsedText', + marginLeft: `${theme.spacing.gridSize * 2}px`, + fontSize: theme.typography.bodySmall.fontSize, + color: theme.colors.text.secondary, + }), timeRange: css({ display: 'inline-block', }), diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx index a00e20809e1..27766f87131 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/AcknowledgeCheckboxes.tsx @@ -110,7 +110,7 @@ export const AcknowledgeCheckboxes = ({ }; const getStyles = (theme: GrafanaTheme2) => ({ - title: css` - font-weight: ${theme.typography.fontWeightBold}; - `, + title: css({ + fontWeight: theme.typography.fontWeightBold, + }), }); diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/CreatePublicDashboard.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/CreatePublicDashboard.tsx index 241949ae6ea..14fc23a0afb 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/CreatePublicDashboard.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/CreatePublicDashboard/CreatePublicDashboard.tsx @@ -110,27 +110,27 @@ export function CreatePublicDashboard({ hasError }: { hasError?: boolean }) { } const getStyles = (theme: GrafanaTheme2) => ({ - container: css` - display: flex; - flex-direction: column; - gap: ${theme.spacing(4)}; - `, - title: css` - font-size: ${theme.typography.h4.fontSize}; - margin: ${theme.spacing(0, 0, 2)}; - `, - description: css` - color: ${theme.colors.text.secondary}; - margin-bottom: ${theme.spacing(0)}; - `, - checkboxes: css` - margin: ${theme.spacing(0, 0, 4)}; - `, - buttonContainer: css` - display: flex; - justify-content: end; - `, - loadingSpinner: css` - margin-left: ${theme.spacing(1)}; - `, + container: css({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(4), + }), + title: css({ + fontSize: theme.typography.h4.fontSize, + margin: theme.spacing(0, 0, 2), + }), + description: css({ + color: theme.colors.text.secondary, + marginBottom: theme.spacing(0), + }), + checkboxes: css({ + margin: theme.spacing(0, 0, 4), + }), + buttonContainer: css({ + display: 'flex', + justifyContent: 'end', + }), + loadingSpinner: css({ + marginLeft: theme.spacing(1), + }), }); diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx index 20edf398316..0db873e5e29 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ModalAlerts/UnsupportedDataSourcesAlert.tsx @@ -38,8 +38,8 @@ export const UnsupportedDataSourcesAlert = ({ unsupportedDataSources }: { unsupp }; const getStyles = (theme: GrafanaTheme2) => ({ - unsupportedDataSourceDescription: css` - color: ${theme.colors.text.secondary}; - margin-bottom: ${theme.spacing(1)}; - `, + unsupportedDataSourceDescription: css({ + color: theme.colors.text.secondary, + marginBottom: theme.spacing(1), + }), }); diff --git a/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx b/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx index cb2fceb8d00..db810281b99 100644 --- a/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx +++ b/public/app/features/dashboard/components/SubMenu/AnnotationPicker.tsx @@ -68,22 +68,20 @@ export const AnnotationPicker = ({ annotation, events, onEnabledChanged }: Annot function getStyles(theme: GrafanaTheme2) { return { - annotation: css` - display: inline-block; - margin-right: ${theme.spacing(1)}; - - .fa-caret-down { - font-size: 75%; - padding-left: ${theme.spacing(1)}; - } - - .gf-form-inline .gf-form { - margin-bottom: 0; - } - `, - indicator: css` - align-self: center; - padding: 0 ${theme.spacing(0.5)}; - `, + annotation: css({ + display: 'inline-block', + marginRight: theme.spacing(1), + '.fa-caret-down': { + fontSize: '75%', + paddingLeft: theme.spacing(1), + }, + '.gf-form-inline .gf-form': { + marginBottom: 0, + }, + }), + indicator: css({ + alignSelf: 'center', + padding: `0 ${theme.spacing(0.5)}`, + }), }; } diff --git a/public/app/features/dashboard/components/SubMenu/SubMenu.tsx b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx index 85e77fc787a..6b1eea31d6a 100644 --- a/public/app/features/dashboard/components/SubMenu/SubMenu.tsx +++ b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx @@ -80,20 +80,19 @@ const mapStateToProps: MapStateToProps = ( const getStyles = stylesFactory((theme: GrafanaTheme2) => { return { - formStyles: css` - display: flex; - flex-wrap: wrap; - display: contents; - `, - submenu: css` - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-content: flex-start; - align-items: flex-start; - gap: ${theme.spacing(1)} ${theme.spacing(2)}; - padding: 0 0 ${theme.spacing(1)} 0; - `, + formStyles: css({ + display: 'contents', + flexWrap: 'wrap', + }), + submenu: css({ + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + alignContent: 'flex-start', + alignItems: 'flex-start', + gap: `${theme.spacing(1)} ${theme.spacing(2)}`, + padding: `0 0 ${theme.spacing(1)} 0`, + }), spacer: css({ flexGrow: 1, }), diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx index 671bbdaf5b5..fa092978cda 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx @@ -83,7 +83,7 @@ export const TransformationEditor = ({ ); return ( -
+
{editor} {debugMode && ( @@ -113,76 +113,75 @@ export const TransformationEditor = ({ const getStyles = (theme: GrafanaTheme2) => { return { - title: css` - display: flex; - padding: 4px 8px 4px 8px; - position: relative; - height: 35px; - border-radius: 4px 4px 0 0; - flex-wrap: nowrap; - justify-content: space-between; - align-items: center; - `, - name: css` - font-weight: ${theme.typography.fontWeightMedium}; - color: ${theme.colors.primary.text}; - `, - iconRow: css` - display: flex; - `, - icon: css` - background: transparent; - border: none; - box-shadow: none; - cursor: pointer; - color: ${theme.colors.text.secondary}; - margin-left: ${theme.spacing(1)}; - &:hover { - color: ${theme.colors.text}; - } - `, - editor: css``, - debugWrapper: css` - display: flex; - flex-direction: row; - `, - debugSeparator: css` - width: 48px; - min-height: 300px; - display: flex; - align-items: center; - align-self: stretch; - justify-content: center; - margin: 0 ${theme.spacing(0.5)}; - color: ${theme.colors.primary.text}; - `, - debugTitle: css` - padding: ${theme.spacing(1)} ${theme.spacing(0.25)}; - font-family: ${theme.typography.fontFamilyMonospace}; - font-size: ${theme.typography.bodySmall.fontSize}; - color: ${theme.colors.text}; - border-bottom: 1px solid ${theme.colors.border.weak}; - flex-grow: 0; - flex-shrink: 1; - `, - - debug: css` - margin-top: ${theme.spacing(1)}; - padding: 0 ${theme.spacing(1, 1, 1)}; - border: 1px solid ${theme.colors.border.weak}; - background: ${theme.isLight ? theme.v1.palette.white : theme.v1.palette.gray05}; - border-radius: ${theme.shape.radius.default}; - width: 100%; - min-height: 300px; - display: flex; - flex-direction: column; - align-self: stretch; - `, - debugJson: css` - flex-grow: 1; - height: 100%; - overflow: hidden; - padding: ${theme.spacing(0.5)}; - `, + title: css({ + display: 'flex', + padding: '4px 8px 4px 8px', + position: 'relative', + height: '35px', + // eslint-disable-next-line @grafana/no-border-radius-literal + borderRadius: '4px 4px 0 0', + flexWrap: 'nowrap', + justifyContent: 'space-between', + alignItems: 'center', + }), + name: css({ + fontWeight: theme.typography.fontWeightMedium, + color: theme.colors.primary.text, + }), + iconRow: css({ + display: 'flex', + }), + icon: css({ + background: 'transparent', + border: 'none', + boxShadow: 'none', + cursor: 'pointer', + color: theme.colors.text.secondary, + marginLeft: theme.spacing(1), + '&:hover': { + color: theme.colors.text.primary, + }, + }), + debugWrapper: css({ + display: 'flex', + flexDirection: 'row', + }), + debugSeparator: css({ + width: '48px', + minHeight: '300px', + display: 'flex', + alignItems: 'center', + alignSelf: 'stretch', + justifyContent: 'center', + margin: `0 ${theme.spacing(0.5)}`, + color: theme.colors.primary.text, + }), + debugTitle: css({ + padding: `${theme.spacing(1)} ${theme.spacing(0.25)}`, + fontFamily: theme.typography.fontFamilyMonospace, + fontSize: theme.typography.bodySmall.fontSize, + color: theme.colors.text.primary, + borderBottom: `1px solid ${theme.colors.border.weak}`, + flexGrow: 0, + flexShrink: 1, + }), + debug: css({ + marginTop: theme.spacing(1), + padding: `0 ${theme.spacing(1, 1, 1)}`, + border: `1px solid ${theme.colors.border.weak}`, + background: `${theme.isLight ? theme.v1.palette.white : theme.v1.palette.gray05}`, + borderRadius: theme.shape.radius.default, + width: '100%', + minHeight: '300px', + display: 'flex', + flexDirection: 'column', + alignSelf: 'stretch', + }), + debugJson: css({ + flexGrow: 1, + height: '100%', + overflow: 'hidden', + padding: theme.spacing(0.5), + }), }; }; diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationPickerNg.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationPickerNg.tsx index 52b0c0ab2ba..2acc57372e9 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationPickerNg.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationPickerNg.tsx @@ -232,16 +232,16 @@ function TransformationsGrid({ showIllustrations, transformations, onClick, data function getTransformationGridStyles(theme: GrafanaTheme2) { return { - // eslint-disable-next-line @emotion/syntax-preference - heading: css` - font-weight: 400, - > button: { - width: '100%', - display: 'flex', - justify-content: 'space-between', - align-items: 'center', - flex-wrap: 'no-wrap', - },`, + heading: css({ + fontWeight: 400, + '> button': { + width: '100%', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + flexWrap: 'nowrap', + }, + }), description: css({ fontSize: '12px', display: 'flex', From dff7cb9afba478402aee18ec26cf21882f8460ac Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Thu, 25 Apr 2024 15:20:37 -0400 Subject: [PATCH 124/222] Alerting: Move alertmanager api silence code to separate files (#86947) * Move alertmanager api silence code to separate files unchanged * Replace with silence model instead interface --------- Co-authored-by: Matt Jacobson --- .../ngalert/accesscontrol/silences.go | 17 +- .../ngalert/accesscontrol/silences_test.go | 108 +++++----- pkg/services/ngalert/api/api_alertmanager.go | 97 --------- .../ngalert/api/api_alertmanager_silences.go | 112 ++++++++++ .../api/api_alertmanager_silences_test.go | 194 ++++++++++++++++++ .../ngalert/api/api_alertmanager_test.go | 181 +--------------- pkg/services/ngalert/models/silence.go | 11 + 7 files changed, 378 insertions(+), 342 deletions(-) create mode 100644 pkg/services/ngalert/api/api_alertmanager_silences.go create mode 100644 pkg/services/ngalert/api/api_alertmanager_silences_test.go create mode 100644 pkg/services/ngalert/models/silence.go diff --git a/pkg/services/ngalert/accesscontrol/silences.go b/pkg/services/ngalert/accesscontrol/silences.go index 178e5ffa4d0..9d9666b674a 100644 --- a/pkg/services/ngalert/accesscontrol/silences.go +++ b/pkg/services/ngalert/accesscontrol/silences.go @@ -9,6 +9,7 @@ import ( ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/ngalert/models" ) const ( @@ -78,10 +79,6 @@ var ( } ) -type Silence interface { - GetRuleUID() *string -} - type RuleUIDToNamespaceStore interface { GetNamespacesByRuleUID(ctx context.Context, orgID int64, uids ...string) (map[string]string, error) } @@ -104,7 +101,7 @@ func NewSilenceService(ac ac.AccessControl, store RuleUIDToNamespaceStore) *Sile // Global silence (one that is not attached to a particular rule) is considered available to all users. // For silences that are not attached to a rule, are checked against authorization. // This method is more preferred when many silences need to be checked. -func (s SilenceService) FilterByAccess(ctx context.Context, user identity.Requester, silences ...Silence) ([]Silence, error) { +func (s SilenceService) FilterByAccess(ctx context.Context, user identity.Requester, silences ...*models.Silence) ([]*models.Silence, error) { canAll, err := s.HasAccess(ctx, user, readAllSilencesEvaluator) if err != nil || canAll { // return early if user can either read all silences or there is an error return silences, err @@ -113,8 +110,8 @@ func (s SilenceService) FilterByAccess(ctx context.Context, user identity.Reques if err != nil || !canSome { return nil, err } - result := make([]Silence, 0, len(silences)) - silencesByRuleUID := make(map[string][]Silence, len(silences)) + result := make([]*models.Silence, 0, len(silences)) + silencesByRuleUID := make(map[string][]*models.Silence, len(silences)) for _, silence := range silences { ruleUID := silence.GetRuleUID() if ruleUID == nil { // if this is a general silence @@ -154,7 +151,7 @@ func (s SilenceService) FilterByAccess(ctx context.Context, user identity.Reques } // AuthorizeReadSilence checks if user has access to read a silence -func (s SilenceService) AuthorizeReadSilence(ctx context.Context, user identity.Requester, silence Silence) error { +func (s SilenceService) AuthorizeReadSilence(ctx context.Context, user identity.Requester, silence *models.Silence) error { canAll, err := s.HasAccess(ctx, user, readAllSilencesEvaluator) if canAll || err != nil { // return early if user can either read all silences or there is error return err @@ -186,7 +183,7 @@ func (s SilenceService) AuthorizeReadSilence(ctx context.Context, user identity. } // AuthorizeCreateSilence checks if user has access to create a silence. Returns ErrAuthorizationBase if user is not authorized -func (s SilenceService) AuthorizeCreateSilence(ctx context.Context, user identity.Requester, silence Silence) error { +func (s SilenceService) AuthorizeCreateSilence(ctx context.Context, user identity.Requester, silence *models.Silence) error { canAny, err := s.HasAccess(ctx, user, createAnySilenceEvaluator) if err != nil || canAny { // return early if user can either create any silence or there is an error @@ -215,7 +212,7 @@ func (s SilenceService) AuthorizeCreateSilence(ctx context.Context, user identit } // AuthorizeUpdateSilence checks if user has access to update\expire a silence. Returns ErrAuthorizationBase if user is not authorized -func (s SilenceService) AuthorizeUpdateSilence(ctx context.Context, user identity.Requester, silence Silence) error { +func (s SilenceService) AuthorizeUpdateSilence(ctx context.Context, user identity.Requester, silence *models.Silence) error { canAny, err := s.HasAccess(ctx, user, updateAnySilenceEvaluator) if err != nil || canAny { // return early if user can either update any silence or there is an error diff --git a/pkg/services/ngalert/accesscontrol/silences_test.go b/pkg/services/ngalert/accesscontrol/silences_test.go index 1d85886032b..570c51f4773 100644 --- a/pkg/services/ngalert/accesscontrol/silences_test.go +++ b/pkg/services/ngalert/accesscontrol/silences_test.go @@ -11,6 +11,7 @@ import ( ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils" ) @@ -18,15 +19,15 @@ import ( var orgID = rand.Int63() func TestFilterByAccess(t *testing.T) { - global := testSilence{ID: "global", RuleUID: nil} - ruleSilence1 := testSilence{ID: "rule-1", RuleUID: utils.Pointer("rule-1-uid")} + global := testSilence("global", nil) + ruleSilence1 := testSilence("rule-1", utils.Pointer("rule-1-uid")) folder1 := "rule-1-folder-uid" folder1Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder1) - ruleSilence2 := testSilence{ID: "rule-2", RuleUID: utils.Pointer("rule-2-uid")} + ruleSilence2 := testSilence("rule-2", utils.Pointer("rule-2-uid")) folder2 := "rule-2-folder-uid" - notFoundRule := testSilence{ID: "unknown-rule", RuleUID: utils.Pointer("unknown-rule-uid")} + notFoundRule := testSilence("unknown-rule", utils.Pointer("unknown-rule-uid")) - silences := []Silence{ + silences := []*models.Silence{ global, ruleSilence1, ruleSilence2, @@ -36,19 +37,19 @@ func TestFilterByAccess(t *testing.T) { testCases := []struct { name string user identity.Requester - expected []Silence + expected []*models.Silence expectedDbAccess bool }{ { name: "no silence access, empty list", user: newUser(), - expected: []Silence{}, + expected: []*models.Silence{}, expectedDbAccess: false, }, { name: "instance reader should get all", user: newUser(ac.Permission{Action: instancesRead}), - expected: []Silence{ + expected: []*models.Silence{ global, ruleSilence1, ruleSilence2, @@ -59,7 +60,7 @@ func TestFilterByAccess(t *testing.T) { { name: "silence reader should get global + folder", user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}), - expected: []Silence{ + expected: []*models.Silence{ global, ruleSilence1, }, @@ -71,8 +72,8 @@ func TestFilterByAccess(t *testing.T) { ac := &recordingAccessControlFake{} store := &fakeRuleUIDToNamespaceStore{ Response: map[string]string{ - *ruleSilence1.RuleUID: folder1, - *ruleSilence2.RuleUID: folder2, + *ruleSilence1.GetRuleUID(): folder1, + *ruleSilence2.GetRuleUID(): folder2, }, } svc := NewSilenceService(ac, store) @@ -93,60 +94,60 @@ func TestFilterByAccess(t *testing.T) { } func TestAuthorizeReadSilence(t *testing.T) { - global := testSilence{ID: "global", RuleUID: nil} - ruleSilence1 := testSilence{ID: "rule-1", RuleUID: utils.Pointer("rule-1-uid")} + global := testSilence("global", nil) + ruleSilence1 := testSilence("rule-1", utils.Pointer("rule-1-uid")) folder1 := "rule-1-folder-uid" folder1Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder1) - ruleSilence2 := testSilence{ID: "rule-2", RuleUID: utils.Pointer("rule-2-uid")} + ruleSilence2 := testSilence("rule-2", utils.Pointer("rule-2-uid")) folder2 := "rule-2-folder-uid" - notFoundRule := testSilence{ID: "unknown-rule", RuleUID: utils.Pointer("unknown-rule-uid")} + notFoundRule := testSilence("unknown-rule", utils.Pointer("unknown-rule-uid")) testCases := []struct { name string user identity.Requester - silence []testSilence + silence []*models.Silence expectedErr error expectedDbAccess bool }{ { name: "not authorized without permissions", user: newUser(), - silence: []testSilence{global, ruleSilence1, notFoundRule}, + silence: []*models.Silence{global, ruleSilence1, notFoundRule}, expectedErr: ErrAuthorizationBase, expectedDbAccess: false, }, { name: "instance reader can read any silence", user: newUser(ac.Permission{Action: instancesRead}), - silence: []testSilence{global, ruleSilence1, notFoundRule}, + silence: []*models.Silence{global, ruleSilence1, notFoundRule}, expectedErr: nil, expectedDbAccess: false, }, { name: "silence reader can read global", user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}), - silence: []testSilence{global}, + silence: []*models.Silence{global}, expectedErr: nil, expectedDbAccess: false, }, { name: "silence reader can read from allowed folder", user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}), - silence: []testSilence{ruleSilence1}, + silence: []*models.Silence{ruleSilence1}, expectedErr: nil, expectedDbAccess: true, }, { name: "silence reader cannot read from other folders", user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}), - silence: []testSilence{ruleSilence2}, + silence: []*models.Silence{ruleSilence2}, expectedErr: ErrAuthorizationBase, expectedDbAccess: true, }, { name: "silence reader cannot read unknown rule", user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}), - silence: []testSilence{notFoundRule}, + silence: []*models.Silence{notFoundRule}, expectedErr: ErrAuthorizationBase, expectedDbAccess: true, }, @@ -155,12 +156,12 @@ func TestAuthorizeReadSilence(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { for _, silence := range testCase.silence { - t.Run(silence.ID, func(t *testing.T) { + t.Run(*silence.ID, func(t *testing.T) { ac := &recordingAccessControlFake{} store := &fakeRuleUIDToNamespaceStore{ Response: map[string]string{ - *ruleSilence1.RuleUID: folder1, - *ruleSilence2.RuleUID: folder2, + *ruleSilence1.GetRuleUID(): folder1, + *ruleSilence2.GetRuleUID(): folder2, }, } svc := NewSilenceService(ac, store) @@ -183,16 +184,16 @@ func TestAuthorizeReadSilence(t *testing.T) { } func TestAuthorizeCreateSilence(t *testing.T) { - global := testSilence{ID: "global", RuleUID: nil} - ruleSilence1 := testSilence{ID: "rule-1", RuleUID: utils.Pointer("rule-1-uid")} + global := testSilence("global", nil) + ruleSilence1 := testSilence("rule-1", utils.Pointer("rule-1-uid")) folder1 := "rule-1-folder-uid" folder1Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder1) - ruleSilence2 := testSilence{ID: "rule-2", RuleUID: utils.Pointer("rule-2-uid")} + ruleSilence2 := testSilence("rule-2", utils.Pointer("rule-2-uid")) folder2 := "rule-2-folder-uid" folder2Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2) - notFoundRule := testSilence{ID: "unknown-rule", RuleUID: utils.Pointer("unknown-rule-uid")} + notFoundRule := testSilence("unknown-rule", utils.Pointer("unknown-rule-uid")) - silences := []testSilence{ + silences := []*models.Silence{ global, ruleSilence1, ruleSilence2, @@ -208,7 +209,7 @@ func TestAuthorizeCreateSilence(t *testing.T) { user identity.Requester expectedErr error expectedDbAccess bool - overrides map[testSilence]override + overrides map[*models.Silence]override }{ { name: "not authorized without permissions", @@ -243,7 +244,7 @@ func TestAuthorizeCreateSilence(t *testing.T) { { name: "instance read + silence create", user: newUser(ac.Permission{Action: silenceCreate, Scope: folder1Scope}, ac.Permission{Action: instancesRead}), - overrides: map[testSilence]override{ + overrides: map[*models.Silence]override{ global: { expectedErr: ErrAuthorizationBase, expectedDbAccess: false, @@ -259,7 +260,7 @@ func TestAuthorizeCreateSilence(t *testing.T) { { name: "silence read + instance create", user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}, ac.Permission{Action: instancesCreate}), - overrides: map[testSilence]override{ + overrides: map[*models.Silence]override{ global: { expectedErr: nil, expectedDbAccess: false, @@ -275,7 +276,7 @@ func TestAuthorizeCreateSilence(t *testing.T) { { name: "silence read + create", user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}, ac.Permission{Action: silenceCreate, Scope: folder1Scope}), - overrides: map[testSilence]override{ + overrides: map[*models.Silence]override{ global: { expectedErr: ErrAuthorizationBase, expectedDbAccess: false, @@ -299,12 +300,12 @@ func TestAuthorizeCreateSilence(t *testing.T) { expectedErr = s.expectedErr expectedDbAccess = s.expectedDbAccess } - t.Run(silence.ID, func(t *testing.T) { + t.Run(*silence.ID, func(t *testing.T) { ac := &recordingAccessControlFake{} store := &fakeRuleUIDToNamespaceStore{ Response: map[string]string{ - *ruleSilence1.RuleUID: folder1, - *ruleSilence2.RuleUID: folder2, + *ruleSilence1.GetRuleUID(): folder1, + *ruleSilence2.GetRuleUID(): folder2, }, } svc := NewSilenceService(ac, store) @@ -328,16 +329,16 @@ func TestAuthorizeCreateSilence(t *testing.T) { } func TestAuthorizeUpdateSilence(t *testing.T) { - global := testSilence{ID: "global", RuleUID: nil} - ruleSilence1 := testSilence{ID: "rule-1", RuleUID: utils.Pointer("rule-1-uid")} + global := testSilence("global", nil) + ruleSilence1 := testSilence("rule-1", utils.Pointer("rule-1-uid")) folder1 := "rule-1-folder-uid" folder1Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder1) - ruleSilence2 := testSilence{ID: "rule-2", RuleUID: utils.Pointer("rule-2-uid")} + ruleSilence2 := testSilence("rule-2", utils.Pointer("rule-2-uid")) folder2 := "rule-2-folder-uid" folder2Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2) - notFoundRule := testSilence{ID: "unknown-rule", RuleUID: utils.Pointer("unknown-rule-uid")} + notFoundRule := testSilence("unknown-rule", utils.Pointer("unknown-rule-uid")) - silences := []testSilence{ + silences := []*models.Silence{ global, ruleSilence1, ruleSilence2, @@ -353,7 +354,7 @@ func TestAuthorizeUpdateSilence(t *testing.T) { user identity.Requester expectedErr error expectedDbAccess bool - overrides map[testSilence]override + overrides map[*models.Silence]override }{ { name: "not authorized without permissions", @@ -388,7 +389,7 @@ func TestAuthorizeUpdateSilence(t *testing.T) { { name: "instance read + silence write", user: newUser(ac.Permission{Action: silenceWrite, Scope: folder1Scope}, ac.Permission{Action: instancesRead}), - overrides: map[testSilence]override{ + overrides: map[*models.Silence]override{ global: { expectedErr: ErrAuthorizationBase, expectedDbAccess: false, @@ -404,7 +405,7 @@ func TestAuthorizeUpdateSilence(t *testing.T) { { name: "silence read + instance write", user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}, ac.Permission{Action: instancesWrite}), - overrides: map[testSilence]override{ + overrides: map[*models.Silence]override{ global: { expectedErr: nil, expectedDbAccess: false, @@ -420,7 +421,7 @@ func TestAuthorizeUpdateSilence(t *testing.T) { { name: "silence read + write", user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}, ac.Permission{Action: silenceWrite, Scope: folder1Scope}), - overrides: map[testSilence]override{ + overrides: map[*models.Silence]override{ global: { expectedErr: ErrAuthorizationBase, expectedDbAccess: false, @@ -444,12 +445,12 @@ func TestAuthorizeUpdateSilence(t *testing.T) { expectedErr = s.expectedErr expectedDbAccess = s.expectedDbAccess } - t.Run(silence.ID, func(t *testing.T) { + t.Run(*silence.ID, func(t *testing.T) { ac := &recordingAccessControlFake{} store := &fakeRuleUIDToNamespaceStore{ Response: map[string]string{ - *ruleSilence1.RuleUID: folder1, - *ruleSilence2.RuleUID: folder2, + *ruleSilence1.GetRuleUID(): folder1, + *ruleSilence2.GetRuleUID(): folder2, }, } svc := NewSilenceService(ac, store) @@ -472,13 +473,8 @@ func TestAuthorizeUpdateSilence(t *testing.T) { } } -type testSilence struct { - ID string - RuleUID *string -} - -func (t testSilence) GetRuleUID() *string { - return t.RuleUID +func testSilence(id string, ruleUID *string) *models.Silence { + return &models.Silence{ID: &id, RuleUID: ruleUID} } type fakeRuleUIDToNamespaceStore struct { diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index 067077326cd..4e9debc356a 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -9,15 +9,12 @@ import ( "strings" "time" - "github.com/go-openapi/strfmt" - alertingNotify "github.com/grafana/alerting/notify" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" - authz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/ngalert/store" @@ -59,50 +56,6 @@ func (srv AlertmanagerSrv) RouteGetAMStatus(c *contextmodel.ReqContext) response return response.JSON(http.StatusOK, status) } -func (srv AlertmanagerSrv) RouteCreateSilence(c *contextmodel.ReqContext, postableSilence apimodels.PostableSilence) response.Response { - err := postableSilence.Validate(strfmt.Default) - if err != nil { - srv.log.Error("Silence failed validation", "error", err) - return ErrResp(http.StatusBadRequest, err, "silence failed validation") - } - - action := accesscontrol.ActionAlertingInstanceUpdate - if postableSilence.ID == "" { - action = accesscontrol.ActionAlertingInstanceCreate - } - evaluator := accesscontrol.EvalPermission(action) - if !accesscontrol.HasAccess(srv.ac, c)(evaluator) { - errAction := "update" - if postableSilence.ID == "" { - errAction = "create" - } - return response.Err(authz.NewAuthorizationErrorWithPermissions(fmt.Sprintf("%s silences", errAction), evaluator)) - } - - silenceID, err := srv.mam.CreateSilence(c.Req.Context(), c.SignedInUser.GetOrgID(), &postableSilence) - if err != nil { - if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) { - return ErrResp(http.StatusNotFound, err, "") - } - if errors.Is(err, notifier.ErrAlertmanagerNotReady) { - return ErrResp(http.StatusConflict, err, "") - } - - if errors.Is(err, alertingNotify.ErrSilenceNotFound) { - return ErrResp(http.StatusNotFound, err, "") - } - - if errors.Is(err, alertingNotify.ErrCreateSilenceBadPayload) { - return ErrResp(http.StatusBadRequest, err, "") - } - - return ErrResp(http.StatusInternalServerError, err, "failed to create silence") - } - return response.JSON(http.StatusAccepted, apimodels.PostSilencesOKBody{ - SilenceID: silenceID, - }) -} - func (srv AlertmanagerSrv) RouteDeleteAlertingConfig(c *contextmodel.ReqContext) response.Response { am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID()) if errResp != nil { @@ -117,22 +70,6 @@ func (srv AlertmanagerSrv) RouteDeleteAlertingConfig(c *contextmodel.ReqContext) return response.JSON(http.StatusAccepted, util.DynMap{"message": "configuration deleted; the default is applied"}) } -func (srv AlertmanagerSrv) RouteDeleteSilence(c *contextmodel.ReqContext, silenceID string) response.Response { - if err := srv.mam.DeleteSilence(c.Req.Context(), c.SignedInUser.GetOrgID(), silenceID); err != nil { - if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) { - return ErrResp(http.StatusNotFound, err, "") - } - if errors.Is(err, notifier.ErrAlertmanagerNotReady) { - return ErrResp(http.StatusConflict, err, "") - } - if errors.Is(err, alertingNotify.ErrSilenceNotFound) { - return ErrResp(http.StatusNotFound, err, "") - } - return ErrResp(http.StatusInternalServerError, err, "") - } - return response.JSON(http.StatusOK, util.DynMap{"message": "silence deleted"}) -} - func (srv AlertmanagerSrv) RouteGetAlertingConfig(c *contextmodel.ReqContext) response.Response { canSeeAutogen := c.SignedInUser.HasRole(org.RoleAdmin) config, err := srv.mam.GetAlertmanagerConfiguration(c.Req.Context(), c.SignedInUser.GetOrgID(), canSeeAutogen) @@ -208,40 +145,6 @@ func (srv AlertmanagerSrv) RouteGetAMAlerts(c *contextmodel.ReqContext) response return response.JSON(http.StatusOK, alerts) } -func (srv AlertmanagerSrv) RouteGetSilence(c *contextmodel.ReqContext, silenceID string) response.Response { - am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID()) - if errResp != nil { - return errResp - } - - gettableSilence, err := am.GetSilence(c.Req.Context(), silenceID) - if err != nil { - if errors.Is(err, alertingNotify.ErrSilenceNotFound) { - return ErrResp(http.StatusNotFound, err, "") - } - // any other error here should be an unexpected failure and thus an internal error - return ErrResp(http.StatusInternalServerError, err, "") - } - return response.JSON(http.StatusOK, gettableSilence) -} - -func (srv AlertmanagerSrv) RouteGetSilences(c *contextmodel.ReqContext) response.Response { - am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID()) - if errResp != nil { - return errResp - } - - gettableSilences, err := am.ListSilences(c.Req.Context(), c.QueryStrings("filter")) - if err != nil { - if errors.Is(err, alertingNotify.ErrListSilencesBadPayload) { - return ErrResp(http.StatusBadRequest, err, "") - } - // any other error here should be an unexpected failure and thus an internal error - return ErrResp(http.StatusInternalServerError, err, "") - } - return response.JSON(http.StatusOK, gettableSilences) -} - func (srv AlertmanagerSrv) RoutePostGrafanaAlertingConfigHistoryActivate(c *contextmodel.ReqContext, id string) response.Response { confId, err := strconv.ParseInt(id, 10, 64) if err != nil { diff --git a/pkg/services/ngalert/api/api_alertmanager_silences.go b/pkg/services/ngalert/api/api_alertmanager_silences.go new file mode 100644 index 00000000000..6226e73a528 --- /dev/null +++ b/pkg/services/ngalert/api/api_alertmanager_silences.go @@ -0,0 +1,112 @@ +package api + +import ( + "errors" + "fmt" + "net/http" + + "github.com/go-openapi/strfmt" + + alertingNotify "github.com/grafana/alerting/notify" + "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/services/accesscontrol" + contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + authz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol" + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/ngalert/notifier" + "github.com/grafana/grafana/pkg/util" +) + +func (srv AlertmanagerSrv) RouteGetSilence(c *contextmodel.ReqContext, silenceID string) response.Response { + am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID()) + if errResp != nil { + return errResp + } + + gettableSilence, err := am.GetSilence(c.Req.Context(), silenceID) + if err != nil { + if errors.Is(err, alertingNotify.ErrSilenceNotFound) { + return ErrResp(http.StatusNotFound, err, "") + } + // any other error here should be an unexpected failure and thus an internal error + return ErrResp(http.StatusInternalServerError, err, "") + } + return response.JSON(http.StatusOK, gettableSilence) +} + +func (srv AlertmanagerSrv) RouteGetSilences(c *contextmodel.ReqContext) response.Response { + am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID()) + if errResp != nil { + return errResp + } + + gettableSilences, err := am.ListSilences(c.Req.Context(), c.QueryStrings("filter")) + if err != nil { + if errors.Is(err, alertingNotify.ErrListSilencesBadPayload) { + return ErrResp(http.StatusBadRequest, err, "") + } + // any other error here should be an unexpected failure and thus an internal error + return ErrResp(http.StatusInternalServerError, err, "") + } + return response.JSON(http.StatusOK, gettableSilences) +} + +func (srv AlertmanagerSrv) RouteCreateSilence(c *contextmodel.ReqContext, postableSilence apimodels.PostableSilence) response.Response { + err := postableSilence.Validate(strfmt.Default) + if err != nil { + srv.log.Error("Silence failed validation", "error", err) + return ErrResp(http.StatusBadRequest, err, "silence failed validation") + } + + action := accesscontrol.ActionAlertingInstanceUpdate + if postableSilence.ID == "" { + action = accesscontrol.ActionAlertingInstanceCreate + } + evaluator := accesscontrol.EvalPermission(action) + if !accesscontrol.HasAccess(srv.ac, c)(evaluator) { + errAction := "update" + if postableSilence.ID == "" { + errAction = "create" + } + return response.Err(authz.NewAuthorizationErrorWithPermissions(fmt.Sprintf("%s silences", errAction), evaluator)) + } + + silenceID, err := srv.mam.CreateSilence(c.Req.Context(), c.SignedInUser.GetOrgID(), &postableSilence) + if err != nil { + if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) { + return ErrResp(http.StatusNotFound, err, "") + } + if errors.Is(err, notifier.ErrAlertmanagerNotReady) { + return ErrResp(http.StatusConflict, err, "") + } + + if errors.Is(err, alertingNotify.ErrSilenceNotFound) { + return ErrResp(http.StatusNotFound, err, "") + } + + if errors.Is(err, alertingNotify.ErrCreateSilenceBadPayload) { + return ErrResp(http.StatusBadRequest, err, "") + } + + return ErrResp(http.StatusInternalServerError, err, "failed to create silence") + } + return response.JSON(http.StatusAccepted, apimodels.PostSilencesOKBody{ + SilenceID: silenceID, + }) +} + +func (srv AlertmanagerSrv) RouteDeleteSilence(c *contextmodel.ReqContext, silenceID string) response.Response { + if err := srv.mam.DeleteSilence(c.Req.Context(), c.SignedInUser.GetOrgID(), silenceID); err != nil { + if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) { + return ErrResp(http.StatusNotFound, err, "") + } + if errors.Is(err, notifier.ErrAlertmanagerNotReady) { + return ErrResp(http.StatusConflict, err, "") + } + if errors.Is(err, alertingNotify.ErrSilenceNotFound) { + return ErrResp(http.StatusNotFound, err, "") + } + return ErrResp(http.StatusInternalServerError, err, "") + } + return response.JSON(http.StatusOK, util.DynMap{"message": "silence deleted"}) +} diff --git a/pkg/services/ngalert/api/api_alertmanager_silences_test.go b/pkg/services/ngalert/api/api_alertmanager_silences_test.go new file mode 100644 index 00000000000..a2279dd0603 --- /dev/null +++ b/pkg/services/ngalert/api/api_alertmanager_silences_test.go @@ -0,0 +1,194 @@ +package api + +import ( + "context" + "math/rand" + "net/http" + "testing" + "time" + + "github.com/go-openapi/strfmt" + amv2 "github.com/prometheus/alertmanager/api/v2/models" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/accesscontrol" + contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/web" +) + +func TestSilenceCreate(t *testing.T) { + makeSilence := func(comment string, createdBy string, + startsAt, endsAt strfmt.DateTime, matchers amv2.Matchers) amv2.Silence { + return amv2.Silence{ + Comment: &comment, + CreatedBy: &createdBy, + StartsAt: &startsAt, + EndsAt: &endsAt, + Matchers: matchers, + } + } + + now := time.Now() + dt := func(t time.Time) strfmt.DateTime { return strfmt.DateTime(t) } + tru := true + testString := "testName" + matchers := amv2.Matchers{&amv2.Matcher{Name: &testString, IsEqual: &tru, IsRegex: &tru, Value: &testString}} + + cases := []struct { + name string + silence amv2.Silence + status int + }{ + {"Valid Silence", + makeSilence("", "tests", dt(now), dt(now.Add(1*time.Second)), matchers), + http.StatusAccepted, + }, + {"No Comment Silence", + func() amv2.Silence { + s := makeSilence("", "tests", dt(now), dt(now.Add(1*time.Second)), matchers) + s.Comment = nil + return s + }(), + http.StatusBadRequest, + }, + } + + for _, cas := range cases { + t.Run(cas.name, func(t *testing.T) { + rc := contextmodel.ReqContext{ + Context: &web.Context{ + Req: &http.Request{}, + }, + SignedInUser: &user.SignedInUser{ + OrgRole: org.RoleEditor, + OrgID: 1, + Permissions: map[int64]map[string][]string{ + 1: {accesscontrol.ActionAlertingInstanceCreate: {}}, + }, + }, + } + + srv := createSut(t) + + resp := srv.RouteCreateSilence(&rc, amv2.PostableSilence{ + ID: "", + Silence: cas.silence, + }) + require.Equal(t, cas.status, resp.Status()) + }) + } +} + +func TestRouteCreateSilence(t *testing.T) { + tesCases := []struct { + name string + silence func() apimodels.PostableSilence + permissions map[int64]map[string][]string + expectedStatus int + }{ + { + name: "new silence, role-based access control is enabled, not authorized", + silence: silenceGen(withEmptyID), + permissions: map[int64]map[string][]string{ + 1: {}, + }, + expectedStatus: http.StatusForbidden, + }, + { + name: "new silence, role-based access control is enabled, authorized", + silence: silenceGen(withEmptyID), + permissions: map[int64]map[string][]string{ + 1: {accesscontrol.ActionAlertingInstanceCreate: {}}, + }, + expectedStatus: http.StatusAccepted, + }, + { + name: "update silence, role-based access control is enabled, not authorized", + silence: silenceGen(), + permissions: map[int64]map[string][]string{ + 1: {accesscontrol.ActionAlertingInstanceCreate: {}}, + }, + expectedStatus: http.StatusForbidden, + }, + { + name: "update silence, role-based access control is enabled, authorized", + silence: silenceGen(), + permissions: map[int64]map[string][]string{ + 1: {accesscontrol.ActionAlertingInstanceUpdate: {}}, + }, + expectedStatus: http.StatusAccepted, + }, + } + + for _, tesCase := range tesCases { + t.Run(tesCase.name, func(t *testing.T) { + sut := createSut(t) + + rc := contextmodel.ReqContext{ + Context: &web.Context{ + Req: &http.Request{}, + }, + SignedInUser: &user.SignedInUser{ + Permissions: tesCase.permissions, + OrgID: 1, + }, + } + + silence := tesCase.silence() + + if silence.ID != "" { + alertmanagerFor, err := sut.mam.AlertmanagerFor(1) + require.NoError(t, err) + silence.ID = "" + newID, err := alertmanagerFor.CreateSilence(context.Background(), &silence) + require.NoError(t, err) + silence.ID = newID + } + + response := sut.RouteCreateSilence(&rc, silence) + require.Equal(t, tesCase.expectedStatus, response.Status()) + }) + } +} + +func silenceGen(mutatorFuncs ...func(*apimodels.PostableSilence)) func() apimodels.PostableSilence { + return func() apimodels.PostableSilence { + testString := util.GenerateShortUID() + isEqual := rand.Int()%2 == 0 + isRegex := rand.Int()%2 == 0 + value := util.GenerateShortUID() + if isRegex { + value = ".*" + util.GenerateShortUID() + } + + matchers := amv2.Matchers{&amv2.Matcher{Name: &testString, IsEqual: &isEqual, IsRegex: &isRegex, Value: &value}} + comment := util.GenerateShortUID() + starts := strfmt.DateTime(timeNow().Add(-time.Duration(rand.Int63n(9)+1) * time.Second)) + ends := strfmt.DateTime(timeNow().Add(time.Duration(rand.Int63n(9)+1) * time.Second)) + createdBy := "User-" + util.GenerateShortUID() + s := apimodels.PostableSilence{ + ID: util.GenerateShortUID(), + Silence: amv2.Silence{ + Comment: &comment, + CreatedBy: &createdBy, + EndsAt: &ends, + Matchers: matchers, + StartsAt: &starts, + }, + } + + for _, mutator := range mutatorFuncs { + mutator(&s) + } + + return s + } +} + +func withEmptyID(silence *apimodels.PostableSilence) { + silence.ID = "" +} diff --git a/pkg/services/ngalert/api/api_alertmanager_test.go b/pkg/services/ngalert/api/api_alertmanager_test.go index da6c5c92d11..a9b04aa3934 100644 --- a/pkg/services/ngalert/api/api_alertmanager_test.go +++ b/pkg/services/ngalert/api/api_alertmanager_test.go @@ -4,23 +4,20 @@ import ( "context" "crypto/md5" "encoding/json" - "math/rand" "net/http" "testing" "time" - "github.com/go-openapi/strfmt" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - alertingNotify "github.com/grafana/alerting/notify" - amv2 "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" + alertingNotify "github.com/grafana/alerting/notify" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -35,7 +32,6 @@ import ( secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -624,141 +620,6 @@ func TestRoutePostTestTemplates(t *testing.T) { }) } -func TestSilenceCreate(t *testing.T) { - makeSilence := func(comment string, createdBy string, - startsAt, endsAt strfmt.DateTime, matchers amv2.Matchers) amv2.Silence { - return amv2.Silence{ - Comment: &comment, - CreatedBy: &createdBy, - StartsAt: &startsAt, - EndsAt: &endsAt, - Matchers: matchers, - } - } - - now := time.Now() - dt := func(t time.Time) strfmt.DateTime { return strfmt.DateTime(t) } - tru := true - testString := "testName" - matchers := amv2.Matchers{&amv2.Matcher{Name: &testString, IsEqual: &tru, IsRegex: &tru, Value: &testString}} - - cases := []struct { - name string - silence amv2.Silence - status int - }{ - {"Valid Silence", - makeSilence("", "tests", dt(now), dt(now.Add(1*time.Second)), matchers), - http.StatusAccepted, - }, - {"No Comment Silence", - func() amv2.Silence { - s := makeSilence("", "tests", dt(now), dt(now.Add(1*time.Second)), matchers) - s.Comment = nil - return s - }(), - http.StatusBadRequest, - }, - } - - for _, cas := range cases { - t.Run(cas.name, func(t *testing.T) { - rc := contextmodel.ReqContext{ - Context: &web.Context{ - Req: &http.Request{}, - }, - SignedInUser: &user.SignedInUser{ - OrgRole: org.RoleEditor, - OrgID: 1, - Permissions: map[int64]map[string][]string{ - 1: {accesscontrol.ActionAlertingInstanceCreate: {}}, - }, - }, - } - - srv := createSut(t) - - resp := srv.RouteCreateSilence(&rc, amv2.PostableSilence{ - ID: "", - Silence: cas.silence, - }) - require.Equal(t, cas.status, resp.Status()) - }) - } -} - -func TestRouteCreateSilence(t *testing.T) { - tesCases := []struct { - name string - silence func() apimodels.PostableSilence - permissions map[int64]map[string][]string - expectedStatus int - }{ - { - name: "new silence, role-based access control is enabled, not authorized", - silence: silenceGen(withEmptyID), - permissions: map[int64]map[string][]string{ - 1: {}, - }, - expectedStatus: http.StatusForbidden, - }, - { - name: "new silence, role-based access control is enabled, authorized", - silence: silenceGen(withEmptyID), - permissions: map[int64]map[string][]string{ - 1: {accesscontrol.ActionAlertingInstanceCreate: {}}, - }, - expectedStatus: http.StatusAccepted, - }, - { - name: "update silence, role-based access control is enabled, not authorized", - silence: silenceGen(), - permissions: map[int64]map[string][]string{ - 1: {accesscontrol.ActionAlertingInstanceCreate: {}}, - }, - expectedStatus: http.StatusForbidden, - }, - { - name: "update silence, role-based access control is enabled, authorized", - silence: silenceGen(), - permissions: map[int64]map[string][]string{ - 1: {accesscontrol.ActionAlertingInstanceUpdate: {}}, - }, - expectedStatus: http.StatusAccepted, - }, - } - - for _, tesCase := range tesCases { - t.Run(tesCase.name, func(t *testing.T) { - sut := createSut(t) - - rc := contextmodel.ReqContext{ - Context: &web.Context{ - Req: &http.Request{}, - }, - SignedInUser: &user.SignedInUser{ - Permissions: tesCase.permissions, - OrgID: 1, - }, - } - - silence := tesCase.silence() - - if silence.ID != "" { - alertmanagerFor, err := sut.mam.AlertmanagerFor(1) - require.NoError(t, err) - silence.ID = "" - newID, err := alertmanagerFor.CreateSilence(context.Background(), &silence) - require.NoError(t, err) - silence.ID = newID - } - - response := sut.RouteCreateSilence(&rc, silence) - require.Equal(t, tesCase.expectedStatus, response.Status()) - }) - } -} - func createSut(t *testing.T) AlertmanagerSrv { t.Helper() @@ -973,44 +834,6 @@ var brokenConfig = ` } }` -func silenceGen(mutatorFuncs ...func(*apimodels.PostableSilence)) func() apimodels.PostableSilence { - return func() apimodels.PostableSilence { - testString := util.GenerateShortUID() - isEqual := rand.Int()%2 == 0 - isRegex := rand.Int()%2 == 0 - value := util.GenerateShortUID() - if isRegex { - value = ".*" + util.GenerateShortUID() - } - - matchers := amv2.Matchers{&amv2.Matcher{Name: &testString, IsEqual: &isEqual, IsRegex: &isRegex, Value: &value}} - comment := util.GenerateShortUID() - starts := strfmt.DateTime(timeNow().Add(-time.Duration(rand.Int63n(9)+1) * time.Second)) - ends := strfmt.DateTime(timeNow().Add(time.Duration(rand.Int63n(9)+1) * time.Second)) - createdBy := "User-" + util.GenerateShortUID() - s := apimodels.PostableSilence{ - ID: util.GenerateShortUID(), - Silence: amv2.Silence{ - Comment: &comment, - CreatedBy: &createdBy, - EndsAt: &ends, - Matchers: matchers, - StartsAt: &starts, - }, - } - - for _, mutator := range mutatorFuncs { - mutator(&s) - } - - return s - } -} - -func withEmptyID(silence *apimodels.PostableSilence) { - silence.ID = "" -} - func createRequestCtxInOrg(org int64) *contextmodel.ReqContext { return &contextmodel.ReqContext{ Context: &web.Context{ diff --git a/pkg/services/ngalert/models/silence.go b/pkg/services/ngalert/models/silence.go new file mode 100644 index 00000000000..dbf9a0946de --- /dev/null +++ b/pkg/services/ngalert/models/silence.go @@ -0,0 +1,11 @@ +package models + +// Silence is the model-layer representation of an alertmanager silence. +type Silence struct { // TODO implement using matchers + ID *string + RuleUID *string +} + +func (s *Silence) GetRuleUID() *string { + return s.RuleUID +} From ff761bb7d6a61979b32f3b0c8ec9858529c9f244 Mon Sep 17 00:00:00 2001 From: Darren Janeczek <38694490+darrenjaneczek@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:27:24 -0400 Subject: [PATCH 125/222] Tooltips: Ensure new viz tooltips are visible within modals (alternative solution) (#86716) fix: ensure new viz tooltips are visible within modals Co-authored-by: Leon Sorokin --- packages/grafana-data/src/themes/zIndex.ts | 1 - .../src/components/uPlot/plugins/TooltipPlugin2.tsx | 11 ++++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/grafana-data/src/themes/zIndex.ts b/packages/grafana-data/src/themes/zIndex.ts index 093c0ef93d4..1423bbaf664 100644 --- a/packages/grafana-data/src/themes/zIndex.ts +++ b/packages/grafana-data/src/themes/zIndex.ts @@ -9,7 +9,6 @@ export const zIndex = { tooltip: 1040, modalBackdrop: 1050, modal: 1060, - tooltipWithinModal: 1060, portal: 1061, }; diff --git a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx index df90a10bc44..daa3f42a12d 100644 --- a/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx +++ b/packages/grafana-ui/src/components/uPlot/plugins/TooltipPlugin2.tsx @@ -1,12 +1,11 @@ import { css, cx } from '@emotion/css'; -import React, { useLayoutEffect, useRef, useReducer, CSSProperties, useContext } from 'react'; +import React, { useLayoutEffect, useRef, useReducer, CSSProperties } from 'react'; import { createPortal } from 'react-dom'; import uPlot from 'uplot'; import { GrafanaTheme2 } from '@grafana/data'; import { DashboardCursorSync } from '@grafana/schema'; -import { ModalsContext } from '../../../components/Modal/ModalsContext'; import { useStyles2 } from '../../../themes'; import { getPortalContainer } from '../../Portal/Portal'; import { UPlotConfigBuilder } from '../config/UPlotConfigBuilder'; @@ -123,10 +122,8 @@ export const TooltipPlugin2 = ({ const sizeRef = useRef(); - const isWithinModal = useContext(ModalsContext).component !== null; - maxWidth = isPinned ? DEFAULT_TOOLTIP_WIDTH : maxWidth ?? DEFAULT_TOOLTIP_WIDTH; - const styles = useStyles2(getStyles, maxWidth, isWithinModal); + const styles = useStyles2(getStyles, maxWidth); const renderRef = useRef(render); renderRef.current = render; @@ -583,11 +580,11 @@ export const TooltipPlugin2 = ({ return null; }; -const getStyles = (theme: GrafanaTheme2, maxWidth?: number, isWithinModal?: boolean) => ({ +const getStyles = (theme: GrafanaTheme2, maxWidth?: number) => ({ tooltipWrapper: css({ top: 0, left: 0, - zIndex: !isWithinModal ? theme.zIndex.tooltip : theme.zIndex.tooltipWithinModal, + zIndex: theme.zIndex.portal, whiteSpace: 'pre', borderRadius: theme.shape.radius.default, position: 'fixed', From 8117aad9afffa73326c35218b85cfd3f93092522 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Fri, 26 Apr 2024 07:17:16 +0200 Subject: [PATCH 126/222] Stack: Add size props (#86900) * Abstract sizing styles from Box * Upd name * Stack: Add sizing props * Revert * Update LoginServiceButtons --- .../src/components/Layout/Box/Box.tsx | 52 +++---------------- .../src/components/Layout/Stack/Stack.tsx | 29 +++++++++-- .../src/components/Layout/utils/styles.ts | 46 ++++++++++++++++ .../components/Login/LoginServiceButtons.tsx | 39 +++++++------- 4 files changed, 95 insertions(+), 71 deletions(-) create mode 100644 packages/grafana-ui/src/components/Layout/utils/styles.ts diff --git a/packages/grafana-ui/src/components/Layout/Box/Box.tsx b/packages/grafana-ui/src/components/Layout/Box/Box.tsx index 34ff3d88611..85354bca07a 100644 --- a/packages/grafana-ui/src/components/Layout/Box/Box.tsx +++ b/packages/grafana-ui/src/components/Layout/Box/Box.tsx @@ -1,5 +1,4 @@ -import { css } from '@emotion/css'; -import { Property } from 'csstype'; +import { css, cx } from '@emotion/css'; import React, { ElementType, forwardRef, PropsWithChildren } from 'react'; import { GrafanaTheme2, ThemeSpacingTokens, ThemeShape, ThemeShadows } from '@grafana/data'; @@ -7,6 +6,7 @@ import { GrafanaTheme2, ThemeSpacingTokens, ThemeShape, ThemeShadows } from '@gr import { useStyles2 } from '../../../themes'; import { AlignItems, Direction, FlexProps, JustifyContent } from '../types'; import { ResponsiveProp, getResponsiveStyle } from '../utils/responsiveness'; +import { getSizeStyles, SizeProps } from '../utils/styles'; type Display = 'flex' | 'block' | 'inline' | 'inline-block' | 'none'; export type BackgroundColor = keyof GrafanaTheme2['colors']['background'] | 'error' | 'success' | 'warning' | 'info'; @@ -15,7 +15,7 @@ export type BorderColor = keyof GrafanaTheme2['colors']['border'] | 'error' | 's export type BorderRadius = keyof ThemeShape['radius']; export type BoxShadow = keyof ThemeShadows; -interface BoxProps extends FlexProps, Omit, 'className' | 'style'> { +interface BoxProps extends FlexProps, SizeProps, Omit, 'className' | 'style'> { // Margin props /** Sets the property `margin` */ margin?: ResponsiveProp; @@ -59,15 +59,6 @@ interface BoxProps extends FlexProps, Omit, 'c justifyContent?: ResponsiveProp; gap?: ResponsiveProp; - // Size props - minWidth?: ResponsiveProp>; - maxWidth?: ResponsiveProp>; - width?: ResponsiveProp>; - - minHeight?: ResponsiveProp>; - maxHeight?: ResponsiveProp>; - height?: ResponsiveProp>; - // Other props backgroundColor?: ResponsiveProp; display?: ResponsiveProp; @@ -145,18 +136,13 @@ export const Box = forwardRef>((props, justifyContent, alignItems, boxShadow, - gap, - width, - minWidth, - maxWidth, - height, - minHeight, - maxHeight + gap ); + const sizeStyles = useStyles2(getSizeStyles, width, minWidth, maxWidth, height, minHeight, maxHeight); const Element = element ?? 'div'; return ( - + {children} ); @@ -217,13 +203,7 @@ const getStyles = ( justifyContent: BoxProps['justifyContent'], alignItems: BoxProps['alignItems'], boxShadow: BoxProps['boxShadow'], - gap: BoxProps['gap'], - width: BoxProps['width'], - minWidth: BoxProps['minWidth'], - maxWidth: BoxProps['maxWidth'], - height: BoxProps['height'], - minHeight: BoxProps['minHeight'], - maxHeight: BoxProps['maxHeight'] + gap: BoxProps['gap'] ) => { return { root: css([ @@ -318,24 +298,6 @@ const getStyles = ( getResponsiveStyle(theme, gap, (val) => ({ gap: theme.spacing(val), })), - getResponsiveStyle(theme, width, (val) => ({ - width: theme.spacing(val), - })), - getResponsiveStyle(theme, minWidth, (val) => ({ - minWidth: theme.spacing(val), - })), - getResponsiveStyle(theme, maxWidth, (val) => ({ - maxWidth: theme.spacing(val), - })), - getResponsiveStyle(theme, height, (val) => ({ - height: theme.spacing(val), - })), - getResponsiveStyle(theme, minHeight, (val) => ({ - minHeight: theme.spacing(val), - })), - getResponsiveStyle(theme, maxHeight, (val) => ({ - maxHeight: theme.spacing(val), - })), ]), }; }; diff --git a/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx b/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx index 835282b8e17..3fb75ec9fcc 100644 --- a/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx +++ b/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx @@ -1,4 +1,4 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2, ThemeSpacingTokens } from '@grafana/data'; @@ -6,8 +6,9 @@ import { GrafanaTheme2, ThemeSpacingTokens } from '@grafana/data'; import { useStyles2 } from '../../../themes'; import { AlignItems, Direction, FlexProps, JustifyContent, Wrap } from '../types'; import { ResponsiveProp, getResponsiveStyle } from '../utils/responsiveness'; +import { getSizeStyles, SizeProps } from '../utils/styles'; -interface StackProps extends FlexProps, Omit, 'className' | 'style'> { +interface StackProps extends FlexProps, SizeProps, Omit, 'className' | 'style'> { gap?: ResponsiveProp; alignItems?: ResponsiveProp; justifyContent?: ResponsiveProp; @@ -17,11 +18,29 @@ interface StackProps extends FlexProps, Omit, } export const Stack = React.forwardRef((props, ref) => { - const { gap = 1, alignItems, justifyContent, direction, wrap, children, grow, shrink, basis, flex, ...rest } = props; + const { + gap = 1, + alignItems, + justifyContent, + direction, + wrap, + children, + grow, + shrink, + basis, + flex, + width, + minWidth, + maxWidth, + height, + minHeight, + maxHeight, + ...rest + } = props; const styles = useStyles2(getStyles, gap, alignItems, justifyContent, direction, wrap, grow, shrink, basis, flex); - + const sizeStyles = useStyles2(getSizeStyles, width, minWidth, maxWidth, height, minHeight, maxHeight); return ( -
+
{children}
); diff --git a/packages/grafana-ui/src/components/Layout/utils/styles.ts b/packages/grafana-ui/src/components/Layout/utils/styles.ts new file mode 100644 index 00000000000..619325d9e75 --- /dev/null +++ b/packages/grafana-ui/src/components/Layout/utils/styles.ts @@ -0,0 +1,46 @@ +import { css } from '@emotion/css'; +import { Property } from 'csstype'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { getResponsiveStyle, ResponsiveProp } from './responsiveness'; + +export interface SizeProps { + minWidth?: ResponsiveProp>; + maxWidth?: ResponsiveProp>; + width?: ResponsiveProp>; + + minHeight?: ResponsiveProp>; + maxHeight?: ResponsiveProp>; + height?: ResponsiveProp>; +} +export const getSizeStyles = ( + theme: GrafanaTheme2, + width: SizeProps['width'], + minWidth: SizeProps['minWidth'], + maxWidth: SizeProps['maxWidth'], + height: SizeProps['height'], + minHeight: SizeProps['minHeight'], + maxHeight: SizeProps['maxHeight'] +) => { + return css([ + getResponsiveStyle(theme, width, (val) => ({ + width: theme.spacing(val), + })), + getResponsiveStyle(theme, minWidth, (val) => ({ + minWidth: theme.spacing(val), + })), + getResponsiveStyle(theme, maxWidth, (val) => ({ + maxWidth: theme.spacing(val), + })), + getResponsiveStyle(theme, height, (val) => ({ + height: theme.spacing(val), + })), + getResponsiveStyle(theme, minHeight, (val) => ({ + minHeight: theme.spacing(val), + })), + getResponsiveStyle(theme, maxHeight, (val) => ({ + maxHeight: theme.spacing(val), + })), + ]); +}; diff --git a/public/app/core/components/Login/LoginServiceButtons.tsx b/public/app/core/components/Login/LoginServiceButtons.tsx index 0bdcba60226..157fe2e7e62 100644 --- a/public/app/core/components/Login/LoginServiceButtons.tsx +++ b/public/app/core/components/Login/LoginServiceButtons.tsx @@ -149,27 +149,24 @@ export const LoginServiceButtons = () => { if (hasServices) { return ( - // TODO: Remove extra div when Stack supports width -
- - - {Object.entries(enabledServices).map(([key, service]) => { - const serviceName = service.name; - return ( - - - Sign in with {{ serviceName }} - - ); - })} - -
+ + + {Object.entries(enabledServices).map(([key, service]) => { + const serviceName = service.name; + return ( + + + Sign in with {{ serviceName }} + + ); + })} + ); } From aec4a23d21cea8dedb2520a3f0054711eabe64fe Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 26 Apr 2024 09:40:23 +0200 Subject: [PATCH 127/222] Chore: Cleanup require sections in go.mod (#86952) --- go.mod | 524 ++++++++++++++++++++++++---------------------------- go.work.sum | 2 + 2 files changed, 244 insertions(+), 282 deletions(-) diff --git a/go.mod b/go.mod index 20f03fd1a38..623c1e4ffa8 100644 --- a/go.mod +++ b/go.mod @@ -19,350 +19,427 @@ replace github.com/prometheus/prometheus => github.com/prometheus/prometheus v0. replace github.com/getkin/kin-openapi => github.com/getkin/kin-openapi v0.122.0 require ( + buf.build/gen/go/parca-dev/parca/bufbuild/connect-go v1.4.1-20221222094228-8b1d3d0f62e6.1 // @grafana/observability-traces-and-profiling + buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.33.0-20240414232344-9ca06271cb73.1 // @grafana/observability-traces-and-profiling + cloud.google.com/go/kms v1.15.7 // @grafana/grafana-backend-group cloud.google.com/go/storage v1.37.0 // @grafana/grafana-backend-group cuelang.org/go v0.6.0-0.dev // @grafana/grafana-as-code + filippo.io/age v1.1.1 // @grafana/identity-access-team github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // @grafana/partner-datasources + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 // @grafana/grafana-backend-group + github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 // @grafana/grafana-backend-group + github.com/Azure/azure-storage-blob-go v0.15.0 // @grafana/grafana-backend-group github.com/Azure/go-autorest/autorest v0.11.29 // @grafana/grafana-backend-group + github.com/Azure/go-autorest/autorest/adal v0.9.23 // @grafana/grafana-backend-group github.com/BurntSushi/toml v1.3.2 // @grafana/identity-access-team github.com/Masterminds/semver v1.5.0 // @grafana/grafana-backend-group + github.com/Masterminds/semver/v3 v3.1.1 // @grafana/grafana-release-guild + github.com/Masterminds/sprig/v3 v3.2.2 // @grafana/grafana-backend-group + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // @grafana/plugins-platform-backend github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // @grafana/grafana-backend-group + github.com/alicebob/miniredis/v2 v2.30.1 // @grafana/alerting-squad-backend + github.com/andybalholm/brotli v1.0.5 // @grafana/partner-datasources + github.com/apache/arrow/go/v15 v15.0.2 // @grafana/observability-metrics + github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad github.com/aws/aws-sdk-go v1.50.29 // @grafana/aws-datasources github.com/beevik/etree v1.2.0 // @grafana/grafana-backend-group github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-squad-backend github.com/blang/semver/v4 v4.0.0 // @grafana/grafana-release-guild + github.com/blugelabs/bluge v0.1.9 // @grafana/grafana-backend-group + github.com/blugelabs/bluge_segment_api v0.2.0 // @grafana/grafana-backend-group github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // @grafana/grafana-backend-group + github.com/bufbuild/connect-go v1.10.0 // @grafana/observability-traces-and-profiling + github.com/bwmarrin/snowflake v0.3.0 // @grafan/grafana-app-platform-squad github.com/centrifugal/centrifuge v0.30.2 // @grafana/grafana-app-platform-squad github.com/crewjam/saml v0.4.13 // @grafana/identity-access-team + github.com/dave/dst v0.27.2 // @grafana/grafana-as-code + github.com/deepmap/oapi-codegen/v2 v2.1.0 // @grafana/grafana-as-code + github.com/dlmiddlecote/sqlstats v1.0.2 // @grafana/grafana-backend-group + github.com/docker/docker v24.0.7+incompatible // @grafana/grafana-release-guild + github.com/drone/drone-cli v1.6.1 // @grafana/grafana-release-guild github.com/fatih/color v1.15.0 // @grafana/grafana-backend-group + github.com/fullstorydev/grpchan v1.1.1 // @grafana/grafana-backend-group github.com/gchaincl/sqlhooks v1.3.0 // @grafana/grafana-search-and-storage + github.com/getkin/kin-openapi v0.124.0 // @grafana/grafana-as-code + github.com/go-jose/go-jose/v3 v3.0.3 // @grafana/identity-access-team + github.com/go-kit/log v0.2.1 // @grafana/grafana-backend-group github.com/go-ldap/ldap/v3 v3.4.4 // @grafana/identity-access-team + github.com/go-openapi/loads v0.21.5 // @grafana/alerting-squad-backend + github.com/go-openapi/runtime v0.27.1 // @grafana/alerting-squad-backend github.com/go-openapi/strfmt v0.22.0 // @grafana/alerting-squad-backend github.com/go-redis/redis/v8 v8.11.5 // @grafana/grafana-backend-group github.com/go-sourcemap/sourcemap v2.1.3+incompatible // @grafana/grafana-backend-group github.com/go-sql-driver/mysql v1.7.1 // @grafana/grafana-search-and-storage github.com/go-stack/stack v1.8.1 // @grafana/grafana-backend-group github.com/gobwas/glob v0.2.3 // @grafana/grafana-backend-group - github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // @grafana/alerting-squad-backend + github.com/golang-jwt/jwt/v4 v4.5.0 // @grafana/grafana-backend-group + github.com/golang-migrate/migrate/v4 v4.7.0 // @grafana/grafana-backend-group github.com/golang/mock v1.6.0 // @grafana/alerting-squad-backend + github.com/golang/protobuf v1.5.4 // @grafana/grafana-backend-group github.com/golang/snappy v0.0.4 // @grafana/alerting-squad-backend github.com/google/go-cmp v0.6.0 // @grafana/grafana-backend-group + github.com/google/go-github v17.0.0+incompatible // @grafana/grafana-release-guild + github.com/google/go-github/v45 v45.2.0 // @grafana/grafana-release-guild github.com/google/uuid v1.6.0 // @grafana/grafana-backend-group github.com/google/wire v0.5.0 // @grafana/grafana-backend-group + github.com/googleapis/gax-go/v2 v2.12.3 // @grafana/grafana-backend-group + github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20240424080142-bb4f4f429d36 // @grafana/alerting-squad-backend + github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 // @grafana/identity-access-team + github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code + github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics + github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics + github.com/grafana/dskit v0.0.0-20240104111617-ea101a3b86eb // @grafana/grafana-backend-group + github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad github.com/grafana/grafana-aws-sdk v0.25.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.0.1 // @grafana/partner-datasources + github.com/grafana/grafana-google-sdk-go v0.1.0 // @grafana/partner-datasources + github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group github.com/grafana/grafana-plugin-sdk-go v0.227.0 // @grafana/plugins-platform-backend + github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad + // This needs to be here for other projects that import grafana/grafana + // For local development grafana/grafana will always use the local files + // Check go.work file for details + github.com/grafana/grafana/pkg/promlib v0.0.5 // @grafana/observability-metrics + github.com/grafana/pyroscope-go/godeltaprof v0.1.6 // @grafana/observability-traces-and-profiling + github.com/grafana/pyroscope/api v0.3.0 // @grafana/observability-traces-and-profiling + github.com/grafana/tempo v1.5.1-0.20230524121406-1dc1bfe7085b // @grafana/observability-traces-and-profiling + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // @grafana/plugins-platform-backend + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // @grafana/grafana-backend-group github.com/hashicorp/go-hclog v1.6.3 // @grafana/plugins-platform-backend + github.com/hashicorp/go-multierror v1.1.1 // @grafana/alerting-squad github.com/hashicorp/go-plugin v1.6.0 // @grafana/plugins-platform-backend github.com/hashicorp/go-version v1.6.0 // @grafana/grafana-backend-group + github.com/hashicorp/golang-lru/v2 v2.0.7 // @grafana/alerting-squad-backend github.com/hashicorp/hcl/v2 v2.17.0 // @grafana/alerting-squad-backend + github.com/huandu/xstrings v1.3.2 // @grafana/partner-datasources github.com/influxdata/influxdb-client-go/v2 v2.13.0 // @grafana/observability-metrics github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // @grafana/grafana-app-platform-squad github.com/jmespath/go-jmespath v0.4.0 // @grafana/grafana-backend-group + github.com/jmoiron/sqlx v1.3.5 // @grafana/grafana-backend-group github.com/json-iterator/go v1.1.12 // @grafana/grafana-backend-group + github.com/krasun/gosqlparser v1.0.5 // @grafana/grafana-app-platform-squad github.com/lib/pq v1.10.9 // @grafana/grafana-backend-group github.com/linkedin/goavro/v2 v2.10.0 // @grafana/grafana-backend-group github.com/m3db/prometheus_remote_client_golang v0.4.4 // @grafana/grafana-backend-group github.com/magefile/mage v1.15.0 // @grafana/grafana-release-guild + github.com/matryer/is v1.4.0 // @grafana/grafana-as-code github.com/mattn/go-isatty v0.0.20 // @grafana/grafana-backend-group github.com/mattn/go-sqlite3 v1.14.19 // @grafana/grafana-backend-group github.com/matttproud/golang_protobuf_extensions v1.0.4 // @grafana/alerting-squad-backend + github.com/microsoft/go-mssqldb v1.6.1-0.20240214161942-b65008136246 // @grafana/grafana-bi-squad + github.com/mitchellh/mapstructure v1.5.0 //@grafana/identity-access-team + github.com/modern-go/reflect2 v1.0.2 // @grafana/alerting-squad-backend + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // @grafana/alerting-squad-backend github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // @grafana/grafana-operator-experience-squad - github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // @grafana/grafana-backend-group github.com/patrickmn/go-cache v2.1.0+incompatible // @grafana/alerting-squad-backend - github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/alertmanager v0.26.0 // @grafana/alerting-squad-backend github.com/prometheus/client_golang v1.19.0 // @grafana/alerting-squad-backend github.com/prometheus/client_model v0.6.1 // @grafana/grafana-backend-group github.com/prometheus/common v0.53.0 // @grafana/alerting-squad-backend github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 // @grafana/alerting-squad-backend + github.com/redis/go-redis/v9 v9.1.0 // @grafana/alerting-squad-backend github.com/robfig/cron/v3 v3.0.1 // @grafana/grafana-backend-group github.com/russellhaering/goxmldsig v1.4.0 // @grafana/grafana-backend-group github.com/scottlepp/go-duck v0.0.15 // @grafana/grafana-app-platform-squad + github.com/spf13/cobra v1.8.0 // @grafana/grafana-app-platform-squad + github.com/spf13/pflag v1.0.5 // @grafana-app-platform-squad + github.com/spyzhov/ajson v0.9.0 // @grafana/grafana-app-platform-squad github.com/stretchr/testify v1.9.0 // @grafana/grafana-backend-group github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf // @grafana/grafana-backend-group github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f // @grafana/grafana-backend-group - github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/urfave/cli v1.22.14 // @grafana/grafana-backend-group github.com/urfave/cli/v2 v2.25.0 // @grafana/grafana-backend-group github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae // @grafana/grafana-backend-group + github.com/wk8/go-ordered-map v1.0.0 // @grafana/grafana-backend-group + github.com/xlab/treeprint v1.2.0 // @grafana/observability-traces-and-profiling + github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // @grafana/grafana-app-platform-squad github.com/yudai/gojsondiff v1.0.0 // @grafana/grafana-backend-group go.opentelemetry.io/collector/pdata v1.5.0 // @grafana/grafana-backend-group + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // @grafana/plugins-platform-backend go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.49.0 // @grafana/grafana-operator-experience-squad + go.opentelemetry.io/contrib/propagators/jaeger v1.22.0 // @grafana/grafana-backend-group + go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel v1.24.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/exporters/jaeger v1.10.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/sdk v1.24.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/trace v1.24.0 // @grafana/grafana-backend-group + go.uber.org/atomic v1.11.0 // @grafana/alerting-squad-backend + go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage + gocloud.dev v0.25.0 // @grafana/grafana-app-platform-squad golang.org/x/crypto v0.22.0 // @grafana/grafana-backend-group golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // @grafana/alerting-squad-backend + golang.org/x/mod v0.15.0 // @grafana/grafana-backend-group golang.org/x/net v0.24.0 // @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.19.0 // @grafana/identity-access-team golang.org/x/sync v0.7.0 // @grafana/alerting-squad-backend + golang.org/x/text v0.14.0 // @grafana/grafana-backend-group golang.org/x/time v0.5.0 // @grafana/grafana-backend-group golang.org/x/tools v0.18.0 // @grafana/grafana-as-code gonum.org/v1/gonum v0.12.0 // @grafana/observability-metrics google.golang.org/api v0.176.0 // @grafana/grafana-backend-group google.golang.org/grpc v1.63.2 // @grafana/plugins-platform-backend google.golang.org/protobuf v1.33.0 // @grafana/plugins-platform-backend - gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.0 // @grafana/alerting-squad-backend gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-squad-backend + k8s.io/api v0.29.2 // @grafana/grafana-app-platform-squad + k8s.io/apimachinery v0.29.2 // @grafana/grafana-app-platform-squad + k8s.io/apiserver v0.29.2 // @grafana/grafana-app-platform-squad + k8s.io/client-go v0.29.2 // @grafana/grafana-app-platform-squad + k8s.io/code-generator v0.29.1 // @grafana/grafana-app-platform-squad + k8s.io/component-base v0.29.2 // @grafana/grafana-app-platform-squad + k8s.io/klog/v2 v2.120.1 // @grafana/grafana-app-platform-squad + k8s.io/kube-aggregator v0.29.0 // @grafana/grafana-app-platform-squad + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // @grafana/grafana-app-platform-squad + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // @grafana/partner-datasources + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // @grafana-app-platform-squad xorm.io/builder v0.3.6 // @grafana/grafana-backend-group xorm.io/core v0.7.3 // @grafana/grafana-backend-group xorm.io/xorm v0.8.2 // @grafana/alerting-squad-backend ) require ( + cloud.google.com/go v0.112.0 // indirect + cloud.google.com/go/auth v0.2.2 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.1 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go/iam v1.1.6 // indirect + github.com/Azure/azure-pipeline-go v0.2.3 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/FZambia/eagle v0.1.0 // indirect - github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect - github.com/andybalholm/brotli v1.0.5 // @grafana/partner-datasources - github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cheekybits/genny v1.0.0 // indirect - github.com/cockroachdb/apd/v2 v2.0.2 // indirect - github.com/dennwc/varint v1.0.0 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/edsrzf/mmap-go v1.1.0 // indirect - github.com/emicklei/proto v1.10.0 // indirect - github.com/go-kit/log v0.2.1 // @grafana/grafana-backend-group - github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-openapi/analysis v0.22.2 // indirect - github.com/go-openapi/errors v0.21.0 // indirect - github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/jsonreference v0.20.4 // indirect - github.com/go-openapi/loads v0.21.5 // @grafana/alerting-squad-backend - github.com/go-openapi/runtime v0.27.1 // @grafana/alerting-squad-backend - github.com/go-openapi/spec v0.20.14 // indirect - github.com/go-openapi/swag v0.22.9 // indirect - github.com/go-openapi/validate v0.23.0 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // @grafana/grafana-backend-group - github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect - github.com/golang/glog v1.2.0 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.4 // @grafana/grafana-backend-group - github.com/google/btree v1.1.2 // indirect - github.com/google/flatbuffers v23.5.26+incompatible // indirect - github.com/googleapis/gax-go/v2 v2.12.3 // @grafana/grafana-backend-group - github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group - github.com/grafana/grafana-google-sdk-go v0.1.0 // @grafana/partner-datasources - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect; @grafana/plugins-platform-backend - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-msgpack v0.5.5 // indirect - github.com/hashicorp/go-multierror v1.1.1 // @grafana/alerting-squad - github.com/hashicorp/go-sockaddr v1.0.6 // indirect - github.com/hashicorp/yamux v0.1.1 // indirect - github.com/igm/sockjs-go/v3 v3.0.2 // indirect - github.com/jessevdk/go-flags v1.5.0 // indirect - github.com/jonboulle/clockwork v0.4.0 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/jpillora/backoff v1.0.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect - github.com/mattetti/filebuffer v1.0.1 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/miekg/dns v1.1.57 // indirect - github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // @grafana/alerting-squad-backend - github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect - github.com/oklog/run v1.1.0 // indirect - github.com/oklog/ulid v1.3.1 // indirect - github.com/olekukonko/tablewriter v0.0.5 // @grafana/grafana-backend-group - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/common/sigv4 v0.1.0 // indirect - github.com/prometheus/exporter-toolkit v0.11.0 // indirect - github.com/prometheus/procfs v0.14.0 // indirect - github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b // indirect - github.com/rs/cors v1.10.1 // indirect - github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect - github.com/segmentio/encoding v0.3.6 // indirect - github.com/sergi/go-diff v1.3.1 // indirect - github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect - github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/uber/jaeger-lib v2.4.1+incompatible // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect - go.mongodb.org/mongo-driver v1.13.1 // indirect - go.opencensus.io v0.24.0 // indirect - go.uber.org/atomic v1.11.0 // @grafana/alerting-squad-backend - go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // @grafana/grafana-backend-group - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect; @grafana/grafana-backend-group -) - -require ( - cloud.google.com/go/kms v1.15.7 // @grafana/grafana-backend-group - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 // @grafana/grafana-backend-group - github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 // @grafana/grafana-backend-group - github.com/Azure/azure-storage-blob-go v0.15.0 // @grafana/grafana-backend-group - github.com/Azure/go-autorest/autorest/adal v0.9.23 // @grafana/grafana-backend-group - github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad - github.com/blugelabs/bluge v0.1.9 // @grafana/grafana-backend-group - github.com/blugelabs/bluge_segment_api v0.2.0 // @grafana/grafana-backend-group - github.com/bufbuild/connect-go v1.10.0 // @grafana/observability-traces-and-profiling - github.com/dlmiddlecote/sqlstats v1.0.2 // @grafana/grafana-backend-group - github.com/drone/drone-cli v1.6.1 // @grafana/grafana-release-guild - github.com/golang-migrate/migrate/v4 v4.7.0 // @grafana/grafana-backend-group - github.com/google/go-github v17.0.0+incompatible // @grafana/grafana-release-guild - github.com/google/go-github/v45 v45.2.0 // @grafana/grafana-release-guild - github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad - github.com/grafana/dskit v0.0.0-20240104111617-ea101a3b86eb // @grafana/grafana-backend-group - github.com/huandu/xstrings v1.3.2 // @grafana/partner-datasources - github.com/jmoiron/sqlx v1.3.5 // @grafana/grafana-backend-group - github.com/matryer/is v1.4.0 // @grafana/grafana-as-code - github.com/urfave/cli v1.22.14 // @grafana/grafana-backend-group - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // @grafana/plugins-platform-backend - go.opentelemetry.io/contrib/propagators/jaeger v1.22.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // @grafana/grafana-backend-group - gocloud.dev v0.25.0 // @grafana/grafana-app-platform-squad -) - -require ( - buf.build/gen/go/parca-dev/parca/bufbuild/connect-go v1.4.1-20221222094228-8b1d3d0f62e6.1 // @grafana/observability-traces-and-profiling - buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.33.0-20240414232344-9ca06271cb73.1 // @grafana/observability-traces-and-profiling - github.com/Masterminds/semver/v3 v3.1.1 // @grafana/grafana-release-guild - github.com/alicebob/miniredis/v2 v2.30.1 // @grafana/alerting-squad-backend - github.com/dave/dst v0.27.2 // @grafana/grafana-as-code - github.com/go-jose/go-jose/v3 v3.0.3 // @grafana/identity-access-team - github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics - github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics - github.com/grafana/tempo v1.5.1-0.20230524121406-1dc1bfe7085b // @grafana/observability-traces-and-profiling - github.com/microsoft/go-mssqldb v1.6.1-0.20240214161942-b65008136246 // @grafana/grafana-bi-squad - github.com/redis/go-redis/v9 v9.1.0 // @grafana/alerting-squad-backend - go.opentelemetry.io/contrib/samplers/jaegerremote v0.18.0 // @grafana/grafana-backend-group - golang.org/x/mod v0.15.0 // @grafana/grafana-backend-group - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // @grafana/partner-datasources -) - -require ( - github.com/spf13/cobra v1.8.0 // @grafana/grafana-app-platform-squad - go.opentelemetry.io/otel v1.24.0 // @grafana/grafana-backend-group - k8s.io/api v0.29.2 // @grafana/grafana-app-platform-squad - k8s.io/apimachinery v0.29.2 // @grafana/grafana-app-platform-squad - k8s.io/apiserver v0.29.2 // @grafana/grafana-app-platform-squad - k8s.io/client-go v0.29.2 // @grafana/grafana-app-platform-squad - k8s.io/component-base v0.29.2 // @grafana/grafana-app-platform-squad - k8s.io/klog/v2 v2.120.1 // @grafana/grafana-app-platform-squad - k8s.io/kube-aggregator v0.29.0 // @grafana/grafana-app-platform-squad - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // @grafana/grafana-app-platform-squad -) - -require github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad - -require github.com/grafana/pyroscope/api v0.3.0 // @grafana/observability-traces-and-profiling - -require github.com/grafana/pyroscope-go/godeltaprof v0.1.6 // @grafana/observability-traces-and-profiling - -require github.com/apache/arrow/go/v15 v15.0.2 // @grafana/observability-metrics - -require ( - cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect - github.com/Azure/azure-pipeline-go v0.2.3 // indirect github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect + github.com/FZambia/eagle v0.1.0 // indirect + github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect + github.com/RoaringBitmap/roaring v0.9.4 // indirect github.com/agext/levenshtein v1.2.1 // indirect + github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect + github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect + github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 // indirect + github.com/apache/thrift v0.18.1 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.2.0 // indirect + github.com/blevesearch/go-porterstemmer v1.0.3 // indirect + github.com/blevesearch/mmap-go v1.0.4 // indirect + github.com/blevesearch/segment v0.9.0 // indirect + github.com/blevesearch/snowballstem v0.9.0 // indirect + github.com/blevesearch/vellum v1.0.7 // indirect + github.com/blugelabs/ice v1.0.0 // indirect github.com/bmatcuk/doublestar v1.1.1 // indirect + github.com/bufbuild/protocompile v0.4.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/buildkite/yaml v2.1.0+incompatible // indirect - github.com/bwmarrin/snowflake v0.3.0 // @grafan/grafana-app-platform-squad + github.com/caio/go-tdigest v3.1.0+incompatible // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/centrifugal/protocol v0.10.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cheekybits/genny v1.0.0 // indirect + github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/cockroachdb/apd/v2 v2.0.2 // indirect + github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dennwc/varint v1.0.0 // indirect + github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/drone-runners/drone-runner-docker v1.8.2 // indirect github.com/drone/drone-go v1.7.1 // indirect github.com/drone/envsubst v1.0.3 // indirect github.com/drone/runner-go v1.12.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/edsrzf/mmap-go v1.1.0 // indirect + github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/proto v1.10.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect; @grafana/grafana-app-platform-squad + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/analysis v0.22.2 // indirect + github.com/go-openapi/errors v0.21.0 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/spec v0.20.14 // indirect + github.com/go-openapi/swag v0.22.9 // indirect + github.com/go-openapi/validate v0.23.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/status v1.1.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/golang/glog v1.2.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/btree v1.1.2 // indirect github.com/google/cel-go v0.17.7 // indirect + github.com/google/flatbuffers v23.5.26+incompatible // indirect + github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db // indirect + github.com/grafana/sqlds/v3 v3.2.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect; @grafana/plugins-platform-backend + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // @grafana/alerting-squad-backend + github.com/hashicorp/go-msgpack v0.5.5 // indirect + github.com/hashicorp/go-sockaddr v1.0.6 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/memberlist v0.5.0 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect + github.com/igm/sockjs-go/v3 v3.0.2 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/jsonschema v0.12.0 // indirect github.com/invopop/yaml v0.2.0 // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/goidentity/v6 v6.0.1 // indirect + github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect + github.com/jcmturner/rpc/v2 v2.0.3 // indirect + github.com/jessevdk/go-flags v1.5.0 // indirect + github.com/jhump/protoreflect v1.15.1 // indirect + github.com/jonboulle/clockwork v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/klauspost/asmfmt v1.3.2 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/text v0.2.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect + github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-ieproxy v0.0.3 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/miekg/dns v1.1.57 // indirect + github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect + github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 //@grafana/identity-access-team + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // @grafana/alerting-squad-backend + github.com/mithrandie/csvq v1.17.10 // indirect + github.com/mithrandie/csvq-driver v1.6.8 // indirect + github.com/mithrandie/go-file/v2 v2.1.0 // indirect + github.com/mithrandie/go-text v1.5.4 // indirect + github.com/mithrandie/ternary v1.1.1 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect + github.com/mschoch/smat v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/oapi-codegen/runtime v1.1.1 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220512140940-7b36cea86235 // indirect github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pierrec/lz4/v4 v4.1.18 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/common/sigv4 v0.1.0 // indirect + github.com/prometheus/exporter-toolkit v0.11.0 // indirect + github.com/prometheus/procfs v0.14.0 // indirect + github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b // indirect github.com/redis/rueidis v1.0.16 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.3.4 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rs/cors v1.10.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/segmentio/asm v1.2.0 // indirect + github.com/segmentio/encoding v0.3.6 // indirect + github.com/sergi/go-diff v1.3.1 // indirect github.com/shopspring/decimal v1.2.0 // indirect + github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect + github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/pflag v1.0.5 // @grafana-app-platform-squad github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect github.com/unknwon/com v1.0.1 // indirect github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + github.com/yudai/pp v2.0.1+incompatible // indirect github.com/yuin/gopher-lua v1.1.0 // indirect github.com/zclconf/go-cty v1.13.0 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.etcd.io/etcd/api/v3 v3.5.10 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect go.etcd.io/etcd/client/v3 v3.5.10 // indirect + go.mongodb.org/mongo-driver v1.13.1 // indirect + go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/term v0.19.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect; @grafana/grafana-backend-group google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/kms v0.29.2 // indirect lukechampine.com/uint128 v1.3.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect @@ -376,126 +453,9 @@ require ( modernc.org/token v1.1.0 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // @grafana-app-platform-squad sigs.k8s.io/yaml v1.3.0 // indirect; @grafana-app-platform-squad ) -require ( - cloud.google.com/go/iam v1.1.6 // indirect - filippo.io/age v1.1.1 // @grafana/identity-access-team - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect - github.com/Masterminds/sprig/v3 v3.2.2 // @grafana/grafana-backend-group - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // @grafana/plugins-platform-backend - github.com/RoaringBitmap/roaring v0.9.4 // indirect - github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f // indirect - github.com/bits-and-blooms/bitset v1.2.0 // indirect - github.com/blevesearch/go-porterstemmer v1.0.3 // indirect - github.com/blevesearch/mmap-go v1.0.4 // indirect - github.com/blevesearch/segment v0.9.0 // indirect - github.com/blevesearch/snowballstem v0.9.0 // indirect - github.com/blevesearch/vellum v1.0.7 // indirect - github.com/blugelabs/ice v1.0.0 // indirect - github.com/caio/go-tdigest v3.1.0+incompatible // indirect - github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 // indirect - github.com/coreos/go-semver v0.3.1 // indirect - github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect - github.com/docker/docker v24.0.7+incompatible // @grafana/grafana-release-guild - github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 // indirect - github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect - github.com/go-logr/logr v1.4.1 // indirect; @grafana/grafana-app-platform-squad - github.com/go-logr/stdr v1.2.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect - github.com/klauspost/compress v1.17.4 // indirect - github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mschoch/smat v0.2.0 // indirect - github.com/pierrec/lz4/v4 v4.1.18 // indirect - github.com/wk8/go-ordered-map v1.0.0 // @grafana/grafana-backend-group - github.com/xlab/treeprint v1.2.0 // @grafana/observability-traces-and-profiling - go.opentelemetry.io/proto/otlp v1.1.0 // indirect -) - -require ( - github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group - github.com/moby/spdystream v0.2.0 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect -) - -require k8s.io/code-generator v0.29.1 // @grafana/grafana-app-platform-squad - -require github.com/spyzhov/ajson v0.9.0 // @grafana/grafana-app-platform-squad - -require github.com/fullstorydev/grpchan v1.1.1 // @grafana/grafana-backend-group - -// This needs to be here for other projects that import grafana/grafana -// For local development grafana/grafana will always use the local files -// Check go.work file for details -require github.com/grafana/grafana/pkg/promlib v0.0.5 // @grafana/observability-metrics - -require ( - github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect - github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 // indirect - github.com/apache/thrift v0.18.1 // indirect - github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad -) - -require ( - github.com/bufbuild/protocompile v0.4.0 // indirect - github.com/grafana/sqlds/v3 v3.2.0 // indirect - github.com/jhump/protoreflect v1.15.1 // indirect - github.com/klauspost/asmfmt v1.3.2 // indirect - github.com/krasun/gosqlparser v1.0.5 // @grafana/grafana-app-platform-squad - github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect - github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mithrandie/csvq v1.17.10 // indirect - github.com/mithrandie/csvq-driver v1.6.8 // indirect - github.com/mithrandie/go-file/v2 v2.1.0 // indirect - github.com/mithrandie/go-text v1.5.4 // indirect - github.com/mithrandie/ternary v1.1.1 // indirect - github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // @grafana/grafana-app-platform-squad -) - -require github.com/getkin/kin-openapi v0.124.0 // @grafana/grafana-as-code - -require github.com/grafana/authlib v0.0.0-20240328140636-a7388d0bac72 // @grafana/identity-access-team - -require github.com/deepmap/oapi-codegen/v2 v2.1.0 // @grafana/grafana-as-code - -require github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // @grafana/plugins-platform-backend - -require github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // @grafana/grafana-backend-group - -require ( - cloud.google.com/go/auth v0.2.2 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.1 // indirect - github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/buger/jsonparser v1.1.1 // indirect - github.com/go-logr/zapr v1.3.0 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/golang-lru v0.6.0 // indirect - github.com/invopop/jsonschema v0.12.0 // indirect - github.com/jcmturner/aescts/v2 v2.0.0 // indirect - github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect - github.com/jcmturner/gofork v1.7.6 // indirect - github.com/jcmturner/goidentity/v6 v6.0.1 // indirect - github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect - github.com/jcmturner/rpc/v2 v2.0.3 // indirect - github.com/oapi-codegen/runtime v1.1.1 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect - github.com/yudai/pp v2.0.1+incompatible // indirect -) - // Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream replace github.com/crewjam/saml => github.com/grafana/saml v0.4.15-0.20231025143828-a6c0e9b86a4c diff --git a/go.work.sum b/go.work.sum index 956533c2c4e..2fa7d95e67a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -651,6 +651,8 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWet github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o= +github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= From f9714b967275ac6a3f6ec6dfe1e23cc5811e426a Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Fri, 26 Apr 2024 10:09:08 +0100 Subject: [PATCH 128/222] Docs: Update manage rbac w. toc and reference to provisioning w. file (#81120) * docs: toc and reference to provisioning * Update docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md Co-authored-by: Jack Baldry --------- Co-authored-by: Jack Baldry --- .../access-control/manage-rbac-roles/index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md b/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md index 98ec10b6308..ea376c5b946 100644 --- a/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md +++ b/docs/sources/administration/roles-and-permissions/access-control/manage-rbac-roles/index.md @@ -20,6 +20,8 @@ weight: 50 Available in [Grafana Enterprise]({{< relref "../../../../introduction/grafana-enterprise/" >}}) and [Grafana Cloud](/docs/grafana-cloud). {{% /admonition %}} +{{< table-of-contents >}} + This section includes instructions for how to view permissions associated with roles, create custom roles, and update and delete roles. The following example includes the base64 username:password Basic Authorization. You cannot use authorization tokens in the request. @@ -104,7 +106,7 @@ Create a custom role when basic roles and fixed roles do not meet your permissio ### Create custom roles using provisioning -File-based provisioning is one method you can use to create custom roles. +[File-based provisioning]({{< relref "./rbac-grafana-provisioning" >}}) is one method you can use to create custom roles. 1. Open the YAML configuration file and locate the `roles` section. From fd30ceed3e83a05d57a53532c2f2395e19de7ed6 Mon Sep 17 00:00:00 2001 From: brendamuir <100768211+brendamuir@users.noreply.github.com> Date: Fri, 26 Apr 2024 12:17:06 +0200 Subject: [PATCH 129/222] Alerting docs: vale fixes (#86972) * Alerting docs: vale fixes * ran prettier --- docs/sources/alerting/fundamentals/_index.md | 8 ++--- .../alert-rules/organising-alerts.md | 2 +- .../alert-rules/rule-evaluation.md | 6 ++-- .../alert-rules/state-and-health.md | 10 +++--- .../fundamentals/notifications/_index.md | 4 +-- .../notifications/alertmanager.md | 4 +-- .../notifications/contact-points.md | 2 +- .../notifications/notification-policies.md | 32 +++++++++---------- 8 files changed, 34 insertions(+), 34 deletions(-) diff --git a/docs/sources/alerting/fundamentals/_index.md b/docs/sources/alerting/fundamentals/_index.md index ce5946e948d..e6030f9e1b7 100644 --- a/docs/sources/alerting/fundamentals/_index.md +++ b/docs/sources/alerting/fundamentals/_index.md @@ -35,15 +35,15 @@ The following concepts are key to your understanding of how Grafana Alerting wor ### Alert rules -An [alert rule][alert-rules] consists of one or more queries and expressions that select the data you want to measure. It also contains a condition, which is the threshold that an alert rule must meet or exceed in order to fire. +An [alert rule][alert-rules] consists of one or more queries and expressions that select the data you want to measure. It also contains a condition, which is the threshold that an alert rule must meet or exceed to fire. Add labels to uniquely identify your alert rule and configure alert routing. Labels link alert rules to notification policies, so you can easily manage which policy should handle which alerts and who gets notified. -Once alert rules are created, they go through various states and transitions. +After alert rules are created, they go through various states and transitions. ### Alert instances -Each alert rule can produce multiple alert instances (also known as alerts) - one alert instance for each time series. This is exceptionally powerful as it allows us to observe multiple series in a single expression. +Each alert rule can produce multiple alert instances (also known as alerts) - one alert instance for each time series. This is exceptionally powerful as it allows you to observe multiple series in a single expression. ```promql sum by(cpu) ( @@ -51,7 +51,7 @@ sum by(cpu) ( ) ``` -A rule using the PromQL expression above creates as many alert instances as the amount of CPUs we are observing after the first evaluation, enabling a single rule to report the status of each CPU. +A rule using the PromQL expression above creates as many alert instances as the amount of CPUs after the first evaluation, enabling a single rule to report the status of each CPU. {{< figure src="/static/img/docs/alerting/unified/multi-dimensional-alert.png" caption="Multiple alert instances from a single alert rule" >}} diff --git a/docs/sources/alerting/fundamentals/alert-rules/organising-alerts.md b/docs/sources/alerting/fundamentals/alert-rules/organising-alerts.md index 09113a1622e..1d7c73834c6 100644 --- a/docs/sources/alerting/fundamentals/alert-rules/organising-alerts.md +++ b/docs/sources/alerting/fundamentals/alert-rules/organising-alerts.md @@ -29,7 +29,7 @@ A namespace contains one or more groups. The rules within a group are run sequen ### Groups -The rules within a group are run sequentially at a regular interval, meaning no rules will be evaluated at the same time and in order of appearance. The default interval is one (1) minute. You can rename Grafana Mimir or Loki rule namespaces and groups, and edit group evaluation intervals. +The rules within a group are run sequentially at a regular interval, meaning no rules are evaluated at the same time and in order of appearance. The default interval is one (1) minute. You can rename Grafana Mimir or Loki rule namespaces and groups, and edit group evaluation intervals. > **Note** If you want rules to be evaluated concurrently and with different intervals, consider storing them in different groups. diff --git a/docs/sources/alerting/fundamentals/alert-rules/rule-evaluation.md b/docs/sources/alerting/fundamentals/alert-rules/rule-evaluation.md index a34a508601c..36f51fa4574 100644 --- a/docs/sources/alerting/fundamentals/alert-rules/rule-evaluation.md +++ b/docs/sources/alerting/fundamentals/alert-rules/rule-evaluation.md @@ -42,7 +42,7 @@ In the pending period, you select the period in which an alert rule can be in br Imagine you have an alert rule evaluation interval set at every 30 seconds and the pending period to 90 seconds. -Evaluation will occur as follows: +Evaluation occurs as follows: [00:30] First evaluation - condition not met. @@ -61,12 +61,12 @@ If the alert rule has a condition that needs to be in breach for a certain amoun - The rule stays in the "pending" state until the condition has been broken for the required amount of time - pending period. -- Once the required time has passed, the rule goes into a "firing" state. +- After the required time has passed, the rule goes into a "firing" state. - If the condition is no longer broken during the pending period, the rule goes back to its normal state. **Note:** -If you want to skip the pending state, you can simply set the pending period to 0. This effectively skips the pending period and your alert rule will start firing as soon as the condition is breached. +If you want to skip the pending state, you can simply set the pending period to 0. This effectively skips the pending period and your alert rule starts firing as soon as the condition is breached. When an alert rule fires, alert instances are produced, which are then sent to the Alertmanager. diff --git a/docs/sources/alerting/fundamentals/alert-rules/state-and-health.md b/docs/sources/alerting/fundamentals/alert-rules/state-and-health.md index 06ab9b8f2f0..f03a70d86e7 100644 --- a/docs/sources/alerting/fundamentals/alert-rules/state-and-health.md +++ b/docs/sources/alerting/fundamentals/alert-rules/state-and-health.md @@ -35,10 +35,10 @@ An alert rule can be in either of the following states: | **Pending** | At least one alert instances returned by the evaluation engine is `Pending`. | | **Firing** | At least one alert instances returned by the evaluation engine is `Firing`. | -The alert rule state is determined by the “worst case” state of the alert instances produced. For example, if one alert instance is firing, the alert rule state will also be firing. +The alert rule state is determined by the “worst case” state of the alert instances produced. For example, if one alert instance is firing, the alert rule state is also firing. {{% admonition type="note" %}} -Alerts will transition first to `pending` and then `firing`, thus it will take at least two evaluation cycles before an alert is fired. +Alerts transition first to `pending` and then `firing`, thus it takes at least two evaluation cycles before an alert is fired. {{% /admonition %}} ## Alert instance state @@ -55,11 +55,11 @@ An alert instance can be in either of the following states: ## Keep last state -An alert rule can be configured to keep the last state when a `NoData` and/or `Error` state is encountered. This will both prevent alerts from firing, and from resolving and re-firing. Just like normal evaluation, the alert rule will transition from `Pending` to `Firing` after the pending period has elapsed. +An alert rule can be configured to keep the last state when a `NoData` and/or `Error` state is encountered. This both prevents alerts from firing, and from resolving and re-firing. Just like normal evaluation, the alert rule transitions from `Pending` to `Firing` after the pending period has elapsed. ## Alert rule health -An alert rule can have one the following health statuses: +An alert rule can have one of the following health statuses: | State | Description | | ---------------------- | -------------------------------------------------------------------------------------------------------- | @@ -70,7 +70,7 @@ An alert rule can have one the following health statuses: ## Special alerts for `NoData` and `Error` -When evaluation of an alert rule produces state `NoData` or `Error`, Grafana Alerting will generate alert instances that have the following additional labels: +When evaluation of an alert rule produces state `NoData` or `Error`, Grafana Alerting generates alert instances that have the following additional labels: | Label | Description | | ------------------ | ---------------------------------------------------------------------- | diff --git a/docs/sources/alerting/fundamentals/notifications/_index.md b/docs/sources/alerting/fundamentals/notifications/_index.md index 693c673beda..f56feb78679 100644 --- a/docs/sources/alerting/fundamentals/notifications/_index.md +++ b/docs/sources/alerting/fundamentals/notifications/_index.md @@ -18,7 +18,7 @@ weight: 110 # Notifications -Choosing how, when, and where to send your alert notifications is an important part of setting up your alerting system. These decisions will have a direct impact on your ability to resolve issues quickly and not miss anything important. +Choosing how, when, and where to send your alert notifications is an important part of setting up your alerting system. These decisions have a direct impact on your ability to resolve issues quickly and not miss anything important. As a first step, define your contact points; where to send your alert notifications to. A contact point is a set of one or more integrations that are used to deliver notifications. Add notification templates to contact points for reuse and consistent messaging in your notifications. @@ -58,4 +58,4 @@ All notifications templates are written in [Go's templating language](https://pk ## Silences -You can use silences to mute notifications from one or more firing rules. Silences do not stop alerts from firing or being resolved, or hide firing alerts in the user interface. A silence lasts as long as its duration which can be configured in minutes, hours, days, months or years. +You can use silences to mute notifications from one or more firing rules. Silences do not stop alerts from firing or being resolved, or hide firing alerts in the user interface. A silence lasts as long as its duration, which can be configured in minutes, hours, days, months, or years. diff --git a/docs/sources/alerting/fundamentals/notifications/alertmanager.md b/docs/sources/alerting/fundamentals/notifications/alertmanager.md index a7a55219947..15cd39ffeca 100644 --- a/docs/sources/alerting/fundamentals/notifications/alertmanager.md +++ b/docs/sources/alerting/fundamentals/notifications/alertmanager.md @@ -26,7 +26,7 @@ Alertmanagers are visible from the drop-down menu on the Alerting Contact Points In Grafana, you can use the Cloud Alertmanager, Grafana Alertmanager, or an external Alertmanager. You can also run multiple Alertmanagers; your decision depends on your set up and where your alerts are being generated. -- **Grafana Alertmanager** is an internal Alertmanager that is pre-configured and available for selection by default if you run Grafana on-premises or open-source. +- **Grafana Alertmanager** is an internal Alertmanager that is pre-configured and available for selection by default if you run Grafana on-premises or open source. The Grafana Alertmanager can receive alerts from Grafana, but it cannot receive alerts from outside Grafana, for example, from Mimir or Loki. Note that inhibition rules are not supported. @@ -34,7 +34,7 @@ In Grafana, you can use the Cloud Alertmanager, Grafana Alertmanager, or an exte - **External Alertmanager** can receive all your Grafana, Loki, Mimir, and Prometheus alerts. External Alertmanagers can be configured and administered from within Grafana itself. -Here are two examples of when you may want to [add your own external alertmanager][configure-alertmanager] and send your alerts there instead of the Grafana Alertmanager: +Here are two examples of when you may want to [add your own external Alertmanager][configure-alertmanager] and send your alerts there instead of the Grafana Alertmanager: 1. You may already have Alertmanagers on-premises in your own Cloud infrastructure that you have set up and still want to use, because you have other alert generators, such as Prometheus. diff --git a/docs/sources/alerting/fundamentals/notifications/contact-points.md b/docs/sources/alerting/fundamentals/notifications/contact-points.md index 98aa75a0f8c..b88e449147a 100644 --- a/docs/sources/alerting/fundamentals/notifications/contact-points.md +++ b/docs/sources/alerting/fundamentals/notifications/contact-points.md @@ -24,7 +24,7 @@ weight: 112 # Contact points -Contact points contain the configuration for sending notifications. A contact point is a list of integrations, each of which sends a notification to a particular email address, service or URL. Contact points can have multiple integrations of the same kind, or a combination of integrations of different kinds. For example, a contact point could contain a Pagerduty integration; an email and Slack integration; or a Pagerduty integration, a Slack integration, and two email integrations. You can also configure a contact point with no integrations; in which case no notifications are sent. +Contact points contain the configuration for sending notifications. A contact point is a list of integrations, each of which sends a notification to a particular email address, service, or URL. Contact points can have multiple integrations of the same kind, or a combination of integrations of different kinds. For example, a contact point could contain a Pagerduty integration; an email and Slack integration; or a Pagerduty integration, a Slack integration, and two email integrations. You can also configure a contact point with no integrations; in which case no notifications are sent. A contact point cannot send notifications until it has been added to a notification policy. A notification policy can only send alerts to one contact point, but a contact point can be added to a number of notification policies at the same time. When an alert matches a notification policy, the alert is sent to the contact point in that notification policy, which then sends a notification to each integration in its configuration. diff --git a/docs/sources/alerting/fundamentals/notifications/notification-policies.md b/docs/sources/alerting/fundamentals/notifications/notification-policies.md index 1e4d1d2dcf6..15883ab6f80 100644 --- a/docs/sources/alerting/fundamentals/notifications/notification-policies.md +++ b/docs/sources/alerting/fundamentals/notifications/notification-policies.md @@ -31,21 +31,21 @@ Notification policies are _not_ a list, but rather are structured according to a Each policy consists of a set of label matchers (0 or more) that specify which labels they are or aren't interested in handling. -For more information on label matching, see [how label matching works][labels-and-label-matchers]. +For more information on label matching, refer to [how label matching works][labels-and-label-matchers]. {{% admonition type="note" %}} -If you haven't configured any label matchers for your notification policy, your notification policy will match _all_ alert instances. This may prevent child policies from being evaluated unless you have enabled **Continue matching siblings** on the notification policy. +If you haven't configured any label matchers for your notification policy, your notification policy matches _all_ alert instances. This may prevent child policies from being evaluated unless you have enabled **Continue matching siblings** on the notification policy. {{% /admonition %}} ## Routing -To determine which notification policy will handle which alert instances, you have to start by looking at the existing set of notification policies, starting with the default notification policy. +To determine which notification policy handles which alert instances, you have to start by looking at the existing set of notification policies, starting with the default notification policy. -If no policies other than the default policy are configured, the default policy will handle the alert instance. +If no policies other than the default policy are configured, the default policy handles the alert instance. -If policies other than the default policy are defined, it will evaluate those notification policies in the order they are displayed. +If policies other than the default policy are defined, it evaluates those notification policies in the order they are displayed. -If a notification policy has label matchers that match the labels of the alert instance, it will descend in to its child policies and, if there are any, will continue to look for any child policies that might have label matchers that further narrow down the set of labels, and so forth until no more child policies have been found. +If a notification policy has label matchers that match the labels of the alert instance, it descends in to its child policies and, if there are any, continues to look for any child policies that might have label matchers that further narrow down the set of labels, and so forth until no more child policies have been found. If no child policies are defined in a notification policy or if none of the child policies have any label matchers that match the alert instance's labels, the default notification policy is used. @@ -63,7 +63,7 @@ Here's a breakdown of how these policies are selected: **Pod stuck in CrashLoop** does not have a `severity` label, so none of its child policies are matched. It does have a `team=operations` label, so the first policy is matched. -The `team=security` policy is not evaluated since we already found a match and **Continue matching siblings** was not configured for that policy. +The `team=security` policy is not evaluated a match was already found and **Continue matching siblings** was not configured for that policy. **Disk Usage – 80%** has both a `team` and `severity` label, and matches a child policy of the operations team. @@ -80,15 +80,15 @@ The following properties are inherited by child policies: - Timing options - Mute timings -Each of these properties can be overwritten by an individual policy should you wish to override the inherited properties. +Each of these properties can be overwritten by an individual policy if you want to override the inherited properties. To inherit a contact point from the parent policy, leave it blank. To override the inherited grouping options, enable **Override grouping**. To override the inherited timing options, enable **Override general timings**. ### Inheritance example -The example below shows how the notification policy tree from our previous example allows the child policies of the `team=operations` to inherit its contact point. +The example below shows how the notification policy tree from the previous example allows the child policies of the `team=operations` to inherit its contact point. -In this way, we can avoid having to specify the same contact point multiple times for each child policy. +In this way, you can avoid having to specify the same contact point multiple times for each child policy. {{< figure src="/media/docs/alerting/notification-inheritance.png" max-width="750px" caption="Notification policy inheritance" >}} @@ -98,15 +98,15 @@ In this way, we can avoid having to specify the same contact point multiple time Grouping is an important feature of Grafana Alerting as it allows you to batch relevant alerts together into a smaller number of notifications. This is particularly important if notifications are delivered to first-responders, such as engineers on-call, where receiving lots of notifications in a short period of time can be overwhelming and in some cases can negatively impact a first-responders ability to respond to an incident. For example, consider a large outage where many of your systems are down. In this case, grouping can be the difference between receiving 1 phone call and 100 phone calls. -You choose how alerts are grouped together using the Group by option in a notification policy. By default, notification policies in Grafana group alerts together by alert rule using the `alertname` and `grafana_folder` labels (since alert names are not unique across multiple folders). Should you wish to group alerts by something other than the alert rule, change the grouping to any other combination of labels. +Choose how alerts are grouped together using the Group by option in a notification policy. By default, notification policies in Grafana group alerts together by alert rule using the `alertname` and `grafana_folder` labels (since alert names are not unique across multiple folders). If you want to group alerts by something other than the alert rule, change the grouping to any other combination of labels. #### Disable grouping -Should you wish to receive every alert as a separate notification, you can do so by grouping by a special label called `...`. This is useful when your alerts are being delivered to an automated system instead of a first-responder. +If you want to receive every alert as a separate notification, you can do so by grouping by a special label called `...`. This is useful when your alerts are being delivered to an automated system instead of a first-responder. #### A single group for all alerts -Should you wish to receive all alerts together in a single notification, you can do so by leaving Group by empty. +If you want to receive all alerts together in a single notification, you can do so by leaving Group by empty. ### Timing options @@ -114,19 +114,19 @@ The timing options decide how often notifications are sent for each group of ale #### Group wait -Group wait is the amount of time Grafana waits before sending the first notification for a new group of alerts. The longer Group wait is the more time you have for other alerts to arrive. The shorter Group wait is the earlier the first notification will be sent, but at the risk of sending incomplete notifications. You should always choose a Group wait that makes the most sense for your use case. +Group wait is the amount of time Grafana waits before sending the first notification for a new group of alerts. The longer Group wait is the more time you have for other alerts to arrive. The shorter Group wait is the earlier the first notification is sent, but at the risk of sending incomplete notifications. You should always choose a Group wait that makes the most sense for your use case. **Default** 30 seconds #### Group interval -Once the first notification has been sent for a new group of alerts, Grafana starts the Group interval timer. This is the amount of time Grafana waits before sending notifications about changes to the group. For example, another firing alert might have just been added to the group while an existing alert might have resolved. If an alert was too late to be included in the first notification due to Group wait, it will be included in subsequent notifications after Group interval. Once Group interval has elapsed, Grafana resets the Group interval timer. This repeats until there are no more alerts in the group after which the group is deleted. +Once the first notification has been sent for a new group of alerts, the Group interval timer starts. This is the amount of wait time before notifications about changes to the group are sent. For example, another firing alert might have just been added to the group while an existing alert might have resolved. If an alert was too late to be included in the first notification due to Group wait, it is included in subsequent notifications after Group interval. Once Group interval has elapsed, Grafana resets the Group interval timer. This repeats until there are no more alerts in the group after which the group is deleted. **Default** 5 minutes #### Repeat interval -Repeat interval decides how often notifications are repeated if the group has not changed since the last notification. You can think of these as reminders that some alerts are still firing. Repeat interval is closely related to Group interval, which means your Repeat interval must not only be greater than or equal to Group interval, but also must be a multiple of Group interval. If Repeat interval is not a multiple of Group interval it will be coerced into one. For example, if your Group interval is 5 minutes, and your Repeat interval is 9 minutes, the Repeat interval will be rounded up to the nearest multiple of 5 which is 10 minutes. +Repeat interval decides how often notifications are repeated if the group has not changed since the last notification. You can think of these as reminders that some alerts are still firing. Repeat interval is closely related to Group interval, which means your Repeat interval must not only be greater than or equal to Group interval, but also must be a multiple of Group interval. If Repeat interval is not a multiple of Group interval it is coerced into one. For example, if your Group interval is 5 minutes, and your Repeat interval is 9 minutes, the Repeat interval is rounded up to the nearest multiple of 5 which is 10 minutes. **Default** 4 hours From eaad38e492eeb165125224c4ed8f0fbe2fbb0997 Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Fri, 26 Apr 2024 13:01:04 +0100 Subject: [PATCH 130/222] TeamLBAC: Add a limit to the docs for the number of rules (#86971) teamlbac: update to include the limit for the rules --- .../administration/data-source-management/teamlbac/_index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/administration/data-source-management/teamlbac/_index.md b/docs/sources/administration/data-source-management/teamlbac/_index.md index 1d87bdbb06d..c8296dc1ef5 100644 --- a/docs/sources/administration/data-source-management/teamlbac/_index.md +++ b/docs/sources/administration/data-source-management/teamlbac/_index.md @@ -35,6 +35,8 @@ To set up Team LBAC for a Loki data source, refer to [Configure Team LBAC](https ## Limitations +- There is a set number of rules to be configured within a datasource, depending on the size of the rules. + - Around ~500-600 rules is the upper limit. - If there are no Team LBAC rules for a user's team, that user can query all logs. - If an administrator is part of a team with Team LBAC rules, those rules are applied to the administrator requests. - Cloud Access Policies (CAP) LBAC rules override Team LBAC rules. From e6f51536bfaf4c8eb7fe1fe813b3f0150aaa6891 Mon Sep 17 00:00:00 2001 From: Kristina Date: Fri, 26 Apr 2024 08:09:31 -0500 Subject: [PATCH 131/222] Explore: Add tests around query history errors (#86810) * WIP build tests * Fix one test :D * Unskip, remove console logging, fix limit test * Add back whitespace --- .../core/history/RichHistoryLocalStorage.ts | 2 +- .../explore/spec/queryHistory.test.tsx | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/public/app/core/history/RichHistoryLocalStorage.ts b/public/app/core/history/RichHistoryLocalStorage.ts index 4e5a1039d85..eee2d841584 100644 --- a/public/app/core/history/RichHistoryLocalStorage.ts +++ b/public/app/core/history/RichHistoryLocalStorage.ts @@ -185,7 +185,7 @@ function cleanUp(richHistory: RichHistoryLocalStorageDTO[]): RichHistoryLocalSto * Ensures the entry can be added. Throws an error if current limit has been hit. * Returns queries that should be saved back giving space for one extra query. */ -function checkLimits(queriesToKeep: RichHistoryLocalStorageDTO[]): { +export function checkLimits(queriesToKeep: RichHistoryLocalStorageDTO[]): { queriesToKeep: RichHistoryLocalStorageDTO[]; limitExceeded: boolean; } { diff --git a/public/app/features/explore/spec/queryHistory.test.tsx b/public/app/features/explore/spec/queryHistory.test.tsx index 7ee48e8962c..22e6472a694 100644 --- a/public/app/features/explore/spec/queryHistory.test.tsx +++ b/public/app/features/explore/spec/queryHistory.test.tsx @@ -4,8 +4,10 @@ import { Props } from 'react-virtualized-auto-sizer'; import { EventBusSrv, serializeStateToUrlParam } from '@grafana/data'; import { config } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; +import store from 'app/core/store'; import { silenceConsoleOutput } from '../../../../test/core/utils/silenceConsoleOutput'; +import * as localStorage from '../../../core/history/RichHistoryLocalStorage'; import { assertDataSourceFilterVisibility, @@ -142,6 +144,55 @@ describe('Explore: Query History', () => { await assertQueryHistory(['{"expr":"query #2"}', '{"expr":"query #1"}']); }); + it('does not add query if quota exceeded error is reached', async () => { + const urlParams = { + left: serializeStateToUrlParam({ + datasource: 'loki', + queries: [{ refId: 'A', expr: 'query #1' }], + range: { from: 'now-1h', to: 'now' }, + }), + }; + + const { datasources } = setupExplore({ urlParams }); + jest.mocked(datasources.loki.query).mockReturnValueOnce(makeLogsQueryResponse()); + await waitForExplore(); + await openQueryHistory(); + + const storeSpy = jest.spyOn(store, 'setObject').mockImplementation(() => { + const error = new Error('QuotaExceededError'); + error.name = 'QuotaExceededError'; + throw error; + }); + + await inputQuery('query #2'); + await runQuery(); + await assertQueryHistory(['{"expr":"query #1"}']); + storeSpy.mockRestore(); + }); + + it('does add query if limit exceeded error is reached', async () => { + const urlParams = { + left: serializeStateToUrlParam({ + datasource: 'loki', + queries: [{ refId: 'A', expr: 'query #1' }], + range: { from: 'now-1h', to: 'now' }, + }), + }; + + const { datasources } = setupExplore({ urlParams }); + jest.mocked(datasources.loki.query).mockReturnValueOnce(makeLogsQueryResponse()); + await waitForExplore(); + await openQueryHistory(); + + jest.spyOn(localStorage, 'checkLimits').mockImplementationOnce((queries) => { + return { queriesToKeep: queries, limitExceeded: true }; + }); + + await inputQuery('query #2'); + await runQuery(); + await assertQueryHistory(['{"expr":"query #2"}', '{"expr":"query #1"}']); + }); + it('add comments to query history', async () => { const urlParams = { left: serializeStateToUrlParam({ From 45effc48d928f2595b7f5d0f0f1a9ef7235ced3d Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Fri, 26 Apr 2024 17:14:31 +0300 Subject: [PATCH 132/222] Auth: ignore non-OAuth2 providers when creating social connectors (#86989) ignore non-oauth2 providers when creating social connectors --- pkg/login/social/socialimpl/service.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/login/social/socialimpl/service.go b/pkg/login/social/socialimpl/service.go index 3dc66ff819e..97dd10af931 100644 --- a/pkg/login/social/socialimpl/service.go +++ b/pkg/login/social/socialimpl/service.go @@ -8,6 +8,7 @@ import ( "net" "net/http" "os" + "slices" "strings" "time" @@ -58,6 +59,11 @@ func ProvideService(cfg *setting.Cfg, } for _, ssoSetting := range allSettings { + // ignore non-oauth2 providers + if !slices.Contains(ssosettings.AllOAuthProviders, ssoSetting.Provider) { + continue + } + info, err := connectors.CreateOAuthInfoFromKeyValues(ssoSetting.Settings) if err != nil { ss.log.Error("Failed to create OAuthInfo for provider", "error", err, "provider", ssoSetting.Provider) From 85c23eed387888b9024913626fb19bedaa025b44 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Fri, 26 Apr 2024 11:34:55 -0300 Subject: [PATCH 133/222] ShareModal: Remove PublicDashboard tab when share panel (#86946) --- .../dashboard-scene/sharing/ShareModal.tsx | 6 +++--- .../dashboard/components/ShareModal/ShareModal.tsx | 14 +++++++------- .../ShareModal/SharePublicDashboard/utilsTest.tsx | 1 - 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/public/app/features/dashboard-scene/sharing/ShareModal.tsx b/public/app/features/dashboard-scene/sharing/ShareModal.tsx index a2c8aed0bbc..3ada3d1fd72 100644 --- a/public/app/features/dashboard-scene/sharing/ShareModal.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareModal.tsx @@ -77,10 +77,10 @@ export class ShareModal extends SceneObjectBase implements Moda if (!panelRef) { tabs.push(...customDashboardTabs.map((Tab) => new Tab({ dashboardRef, modalRef }))); - } - if (isPublicDashboardsEnabled()) { - tabs.push(new SharePublicDashboardTab({ dashboardRef, modalRef })); + if (isPublicDashboardsEnabled()) { + tabs.push(new SharePublicDashboardTab({ dashboardRef, modalRef })); + } } this.setState({ tabs }); diff --git a/public/app/features/dashboard/components/ShareModal/ShareModal.tsx b/public/app/features/dashboard/components/ShareModal/ShareModal.tsx index 50d31b57025..ddbe9cfaa03 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareModal.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareModal.tsx @@ -55,14 +55,14 @@ function getTabs(canEditDashboard: boolean, panel?: PanelModel, activeTab?: stri component: ShareExport, }); tabs.push(...customDashboardTabs); - } - if (isPublicDashboardsEnabled()) { - tabs.push({ - label: t('share-modal.tab-title.public-dashboard-title', 'Public dashboard'), - value: shareDashboardType.publicDashboard, - component: SharePublicDashboard, - }); + if (isPublicDashboardsEnabled()) { + tabs.push({ + label: t('share-modal.tab-title.public-dashboard-title', 'Public dashboard'), + value: shareDashboardType.publicDashboard, + component: SharePublicDashboard, + }); + } } const at = tabs.find((t) => t.value === activeTab); diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/utilsTest.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/utilsTest.tsx index fcac56a11c2..1434710a4ed 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/utilsTest.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/utilsTest.tsx @@ -56,7 +56,6 @@ export const renderSharePublicDashboard = async ( const newProps = Object.assign( { - panel: mockPanel, dashboard: mockDashboard, onDismiss: () => {}, }, From 7783b16e47b1f285cf43d16e0edb0968a73a442c Mon Sep 17 00:00:00 2001 From: Hugo Kiyodi Oshiro Date: Fri, 26 Apr 2024 16:47:38 +0200 Subject: [PATCH 134/222] Plugins: Make grafana-com API URL usage consistent (#86920) Plugins: Fix grafana-com API URL usage --- pkg/plugins/config/config.go | 6 +++--- pkg/plugins/repo/service.go | 2 +- .../angulardetectorsprovider/dynamic.go | 2 +- .../angulardetectorsprovider/dynamic_test.go | 2 +- .../pluginsintegration/angulardetectorsprovider/gcom.go | 2 +- .../keyretriever/dynamic/dynamic_retriever.go | 2 +- .../keyretriever/dynamic/dynamic_retriever_test.go | 8 ++++---- pkg/services/pluginsintegration/pluginconfig/config.go | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/plugins/config/config.go b/pkg/plugins/config/config.go index 9fcf3bba71a..a8081f728a7 100644 --- a/pkg/plugins/config/config.go +++ b/pkg/plugins/config/config.go @@ -18,7 +18,7 @@ type PluginManagementCfg struct { PluginsCDNURLTemplate string - GrafanaComURL string + GrafanaComAPIURL string GrafanaAppURL string @@ -37,7 +37,7 @@ type Features struct { // NewPluginManagementCfg returns a new PluginManagementCfg. func NewPluginManagementCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSettings, pluginsAllowUnsigned []string, pluginsCDNURLTemplate string, appURL string, features Features, angularSupportEnabled bool, - grafanaComURL string, disablePlugins []string, hideAngularDeprecation []string, forwardHostEnvVars []string, + grafanaComAPIURL string, disablePlugins []string, hideAngularDeprecation []string, forwardHostEnvVars []string, ) *PluginManagementCfg { return &PluginManagementCfg{ PluginsPath: pluginsPath, @@ -46,7 +46,7 @@ func NewPluginManagementCfg(devMode bool, pluginsPath string, pluginSettings set PluginsAllowUnsigned: pluginsAllowUnsigned, DisablePlugins: disablePlugins, PluginsCDNURLTemplate: pluginsCDNURLTemplate, - GrafanaComURL: grafanaComURL, + GrafanaComAPIURL: grafanaComAPIURL, GrafanaAppURL: appURL, Features: features, AngularSupportEnabled: angularSupportEnabled, diff --git a/pkg/plugins/repo/service.go b/pkg/plugins/repo/service.go index 704411e2bb4..dea206ef5af 100644 --- a/pkg/plugins/repo/service.go +++ b/pkg/plugins/repo/service.go @@ -22,7 +22,7 @@ type Manager struct { } func ProvideService(cfg *config.PluginManagementCfg) (*Manager, error) { - baseURL, err := url.JoinPath(cfg.GrafanaComURL, "/api/plugins") + baseURL, err := url.JoinPath(cfg.GrafanaComAPIURL, "/plugins") if err != nil { return nil, err } diff --git a/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic.go b/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic.go index bc3146a6c9f..5a4d37b97d5 100644 --- a/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic.go +++ b/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic.go @@ -62,7 +62,7 @@ func ProvideDynamic(cfg *setting.Cfg, store angularpatternsstore.Service) (*Dyna log: log.New("plugin.angulardetectorsprovider.dynamic"), store: store, httpClient: makeHttpClient(), - baseURL: cfg.GrafanaComURL, + baseURL: cfg.GrafanaComAPIURL, backgroundJobInterval: backgroundJobInterval, } d.log.Debug("Providing dynamic angular detection patterns", "baseURL", d.baseURL, "interval", d.backgroundJobInterval) diff --git a/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic_test.go b/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic_test.go index eea3555e903..0550f9c4a19 100644 --- a/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic_test.go +++ b/pkg/services/pluginsintegration/angulardetectorsprovider/dynamic_test.go @@ -580,7 +580,7 @@ func provideDynamic(t *testing.T, gcomURL string, opts ...provideDynamicOpts) *D if opt.cfg == nil { opt.cfg = setting.NewCfg() } - opt.cfg.GrafanaComURL = gcomURL + opt.cfg.GrafanaComAPIURL = gcomURL + "/api" d, err := ProvideDynamic(opt.cfg, opt.store) require.NoError(t, err) return d diff --git a/pkg/services/pluginsintegration/angulardetectorsprovider/gcom.go b/pkg/services/pluginsintegration/angulardetectorsprovider/gcom.go index d2958b01aec..cd55ed448c7 100644 --- a/pkg/services/pluginsintegration/angulardetectorsprovider/gcom.go +++ b/pkg/services/pluginsintegration/angulardetectorsprovider/gcom.go @@ -9,7 +9,7 @@ import ( ) // gcomAngularPatternsPath is the relative path to the GCOM API handler that returns angular detection patterns. -const gcomAngularPatternsPath = "/api/plugins/angular_patterns" +const gcomAngularPatternsPath = "/plugins/angular_patterns" // GCOMPatternType is a pattern type returned by the GCOM API. type GCOMPatternType string diff --git a/pkg/services/pluginsintegration/keyretriever/dynamic/dynamic_retriever.go b/pkg/services/pluginsintegration/keyretriever/dynamic/dynamic_retriever.go index a038caecd0c..f3d982a0bf1 100644 --- a/pkg/services/pluginsintegration/keyretriever/dynamic/dynamic_retriever.go +++ b/pkg/services/pluginsintegration/keyretriever/dynamic/dynamic_retriever.go @@ -115,7 +115,7 @@ func (kr *KeyRetriever) downloadKeys(ctx context.Context) error { Items []ManifestKeys } - url, err := url.JoinPath(kr.cfg.GrafanaComURL, "/api/plugins/ci/keys") // nolint:gosec URL is provided by config + url, err := url.JoinPath(kr.cfg.GrafanaComAPIURL, "/plugins/ci/keys") // nolint:gosec URL is provided by config if err != nil { return err } diff --git a/pkg/services/pluginsintegration/keyretriever/dynamic/dynamic_retriever_test.go b/pkg/services/pluginsintegration/keyretriever/dynamic/dynamic_retriever_test.go index 79288dee477..5c769adaf50 100644 --- a/pkg/services/pluginsintegration/keyretriever/dynamic/dynamic_retriever_test.go +++ b/pkg/services/pluginsintegration/keyretriever/dynamic/dynamic_retriever_test.go @@ -45,7 +45,7 @@ func Test_PublicKeyUpdate(t *testing.T) { cfg := &setting.Cfg{} expectedKey := "fake" s, done := setFakeAPIServer(t, expectedKey, "7e4d0c6a708866e7") - cfg.GrafanaComURL = s.URL + cfg.GrafanaComAPIURL = s.URL + "/api" v := ProvideService(cfg, keystore.ProvideService(kvstore.NewFakeKVStore())) go func() { err := v.Run(context.Background()) @@ -66,7 +66,7 @@ func Test_PublicKeyUpdate(t *testing.T) { cfg := &setting.Cfg{} expectedKey := "fake" s, done := setFakeAPIServer(t, expectedKey, "7e4d0c6a708866e7") - cfg.GrafanaComURL = s.URL + cfg.GrafanaComAPIURL = s.URL + "/api" v := ProvideService(cfg, keystore.ProvideService(kvstore.NewFakeKVStore())) go func() { err := v.Run(context.Background()) @@ -86,7 +86,7 @@ func Test_PublicKeyUpdate(t *testing.T) { cfg := &setting.Cfg{} expectedKey := "fake" s, done := setFakeAPIServer(t, expectedKey, "other") - cfg.GrafanaComURL = s.URL + cfg.GrafanaComAPIURL = s.URL + "/api" v := ProvideService(cfg, keystore.ProvideService(kvstore.NewFakeKVStore())) go func() { err := v.Run(context.Background()) @@ -113,7 +113,7 @@ func Test_PublicKeyUpdate(t *testing.T) { } expectedKey := "fake" s, done := setFakeAPIServer(t, expectedKey, "7e4d0c6a708866e7") - cfg.GrafanaComURL = s.URL + cfg.GrafanaComAPIURL = s.URL + "/api" v := ProvideService(cfg, keystore.ProvideService(kvstore.NewFakeKVStore())) // Simulate an updated key err := v.kv.SetLastUpdated(context.Background()) diff --git a/pkg/services/pluginsintegration/pluginconfig/config.go b/pkg/services/pluginsintegration/pluginconfig/config.go index cfc32a0377d..83ec526c33f 100644 --- a/pkg/services/pluginsintegration/pluginconfig/config.go +++ b/pkg/services/pluginsintegration/pluginconfig/config.go @@ -34,7 +34,7 @@ func ProvidePluginManagementConfig(cfg *setting.Cfg, settingProvider setting.Pro SkipHostEnvVarsEnabled: features.IsEnabledGlobally(featuremgmt.FlagPluginsSkipHostEnvVars), }, cfg.AngularSupportEnabled, - cfg.GrafanaComURL, + cfg.GrafanaComAPIURL, cfg.DisablePlugins, cfg.HideAngularDeprecation, cfg.ForwardHostEnvVars, From c3cde17b33ecc2696b7039315f0a11070ba4b6ee Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Fri, 26 Apr 2024 17:12:47 +0200 Subject: [PATCH 135/222] Dasbhoard scenes: Don't return null when uids are not matching for new dashboards (#86998) Don't return null when uids not matching for new dashboards --- public/app/features/dashboard/containers/DashboardPageProxy.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.tsx index c31f05630b4..1a1bf22c777 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.tsx @@ -54,7 +54,7 @@ function DashboardPageProxy(props: DashboardPageProxyProps) { return null; } - if (dashboard?.value?.dashboard?.uid !== props.match.params.uid) { + if (dashboard?.value?.dashboard?.uid !== props.match.params.uid && dashboard.value?.meta?.isNew !== true) { return null; } From 9a1f9c126fa59659f9a2494551edcfe5f087fc67 Mon Sep 17 00:00:00 2001 From: linoman <2051016+linoman@users.noreply.github.com> Date: Fri, 26 Apr 2024 17:13:28 +0200 Subject: [PATCH 136/222] Replace deprecated layout elements (#86977) --- .betterer.results | 16 ++++------------ .../UserListPublicDashboardPage.tsx | 6 +++--- public/app/features/admin/UserOrgs.tsx | 5 ++--- public/app/features/api-keys/ApiKeysTable.tsx | 6 +++--- .../serviceaccounts/ServiceAccountPage.tsx | 14 +++++++------- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/.betterer.results b/.betterer.results index acb169c5f64..33f5b3eb612 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1359,11 +1359,8 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"] ], - "public/app/features/admin/UserListPublicDashboardPage/UserListPublicDashboardPage.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui/src\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/admin/UserOrgs.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], + [0, 0, 0, "Styles should be written using objects.", "0"], [0, 0, 0, "Styles should be written using objects.", "1"], [0, 0, 0, "Styles should be written using objects.", "2"], [0, 0, 0, "Styles should be written using objects.", "3"], @@ -1379,8 +1376,7 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "13"], [0, 0, 0, "Styles should be written using objects.", "14"], [0, 0, 0, "Styles should be written using objects.", "15"], - [0, 0, 0, "Styles should be written using objects.", "16"], - [0, 0, 0, "Styles should be written using objects.", "17"] + [0, 0, 0, "Styles should be written using objects.", "16"] ], "public/app/features/admin/UserPermissions.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] @@ -2256,9 +2252,8 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], "public/app/features/api-keys/ApiKeysTable.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"], - [0, 0, 0, "Styles should be written using objects.", "2"] + [0, 0, 0, "Styles should be written using objects.", "0"], + [0, 0, 0, "Styles should be written using objects.", "1"] ], "public/app/features/api-keys/MigrateToServiceAccountsCard.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"], @@ -4043,9 +4038,6 @@ exports[`better eslint`] = { "public/app/features/search/utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/serviceaccounts/ServiceAccountPage.tsx:5381": [ - [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] - ], "public/app/features/serviceaccounts/components/ServiceAccountProfile.tsx:5381": [ [0, 0, 0, "Styles should be written using objects.", "0"] ], diff --git a/public/app/features/admin/UserListPublicDashboardPage/UserListPublicDashboardPage.tsx b/public/app/features/admin/UserListPublicDashboardPage/UserListPublicDashboardPage.tsx index 234cdca3f5d..655b80fcced 100644 --- a/public/app/features/admin/UserListPublicDashboardPage/UserListPublicDashboardPage.tsx +++ b/public/app/features/admin/UserListPublicDashboardPage/UserListPublicDashboardPage.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src'; -import { HorizontalGroup, Icon, Tag, Tooltip } from '@grafana/ui/src'; +import { Icon, Stack, Tag, Tooltip } from '@grafana/ui/src'; import { Page } from 'app/core/components/Page/Page'; import { Trans, t } from 'app/core/internationalization'; @@ -60,10 +60,10 @@ export const UserListPublicDashboardPage = () => {
{user.firstSeenAtAge} {user.lastSeenAtAge} - + {user.totalDashboards} dashboard(s) - + diff --git a/public/app/features/admin/UserOrgs.tsx b/public/app/features/admin/UserOrgs.tsx index 5583380263a..644d42d7eb7 100644 --- a/public/app/features/admin/UserOrgs.tsx +++ b/public/app/features/admin/UserOrgs.tsx @@ -6,7 +6,6 @@ import { Button, ConfirmButton, Field, - HorizontalGroup, Icon, Modal, stylesFactory, @@ -386,14 +385,14 @@ export class AddToOrgModal extends PureComponent - + - + ); diff --git a/public/app/features/api-keys/ApiKeysTable.tsx b/public/app/features/api-keys/ApiKeysTable.tsx index cd5946fd67f..452aa370e94 100644 --- a/public/app/features/api-keys/ApiKeysTable.tsx +++ b/public/app/features/api-keys/ApiKeysTable.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import React from 'react'; import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data'; -import { Button, DeleteButton, HorizontalGroup, Icon, Tooltip, useTheme2 } from '@grafana/ui'; +import { Button, DeleteButton, Icon, Stack, Tooltip, useTheme2 } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { AccessControlAction } from 'app/types'; @@ -50,7 +50,7 @@ export const ApiKeysTable = ({ apiKeys, timeZone, onDelete, onMigrate }: Props) {formatLastUsedAtDate(timeZone, key.lastUsedAt)} - + @@ -60,7 +60,7 @@ export const ApiKeysTable = ({ apiKeys, timeZone, onDelete, onMigrate }: Props) onConfirm={() => onDelete(key)} disabled={!contextSrv.hasPermissionInMetadata(AccessControlAction.ActionAPIKeysDelete, key)} /> - +