From 2d3a62eb21311d3ea3cb47b1a27d5607b21efd8c Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Tue, 14 Oct 2025 12:02:28 +0100 Subject: [PATCH 01/75] Dashboards: Use unified search for dashboard links and improve tests (#112201) --- ...ting-dashboard-links-and-variables.spec.ts | 12 +- .../src/handlers/api/search/handlers.ts | 9 + .../v0alpha1/handlers.ts | 9 + .../SubMenu/DashboardLinksDashboard.test.tsx | 242 +++++++++++------- .../SubMenu/DashboardLinksDashboard.tsx | 61 ++--- .../app/features/search/service/searcher.ts | 11 + public/locales/en-US/grafana.json | 3 +- 7 files changed, 226 insertions(+), 121 deletions(-) diff --git a/e2e/old-arch/dashboards-suite/templating-dashboard-links-and-variables.spec.ts b/e2e/old-arch/dashboards-suite/templating-dashboard-links-and-variables.spec.ts index 06e13002db4..a612d23b50d 100644 --- a/e2e/old-arch/dashboards-suite/templating-dashboard-links-and-variables.spec.ts +++ b/e2e/old-arch/dashboards-suite/templating-dashboard-links-and-variables.spec.ts @@ -8,11 +8,19 @@ describe('Templating', () => { it('Tests dashboard links and variables in links', () => { cy.intercept({ method: 'GET', - url: '/api/search?tag=templating&limit=100', + pathname: /search/, + query: { + tag: 'templating', + limit: '100', + }, }).as('tagsTemplatingSearch'); cy.intercept({ method: 'GET', - url: '/api/search?tag=demo&limit=100', + pathname: /search/, + query: { + tag: 'demo', + limit: '100', + }, }).as('tagsDemoSearch'); e2e.flows.openDashboard({ uid: 'yBCC3aKGk' }); diff --git a/packages/grafana-test-utils/src/handlers/api/search/handlers.ts b/packages/grafana-test-utils/src/handlers/api/search/handlers.ts index 84cda34bb0c..ed42d326d46 100644 --- a/packages/grafana-test-utils/src/handlers/api/search/handlers.ts +++ b/packages/grafana-test-utils/src/handlers/api/search/handlers.ts @@ -24,6 +24,7 @@ const getLegacySearchHandler = () => // Workaround for the fixture kind being 'dashboard' instead of 'dash-db' const mappedTypeFilter = typeFilter === 'dash-db' ? 'dashboard' : typeFilter; const starredFilter = new URL(request.url).searchParams.get('starred') || null; + const tagFilter = new URL(request.url).searchParams.getAll('tag') || null; const response = mockTree .filter((filterItem) => { @@ -32,6 +33,14 @@ const getLegacySearchHandler = () => ({ item }) => item.kind !== 'ui', ]; + if (tagFilter && tagFilter.length > 0) { + filters.push(({ item }) => + Boolean( + (item.kind === 'folder' || item.kind === 'dashboard') && item.tags?.some((tag) => tagFilter.includes(tag)) + ) + ); + } + if (starredFilter) { filters.push(({ item }) => mockStarredDashboards.includes(item.uid)); } diff --git a/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts b/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts index f6d079755c5..6981201f81f 100644 --- a/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts +++ b/packages/grafana-test-utils/src/handlers/apis/dashboard.grafana.app/v0alpha1/handlers.ts @@ -23,6 +23,7 @@ const getSearchHandler = () => const typeFilter = new URL(request.url).searchParams.get('type') || null; const nameFilter = new URL(request.url).searchParams.getAll('name'); const mappedTypeFilter = typeFilter ? typeFilterMap[typeFilter] || typeFilter : null; + const tagFilter = new URL(request.url).searchParams.getAll('tag') || null; const filtered = mockTree.filter((filterItem) => { const filters: FilterArray = [ @@ -39,6 +40,14 @@ const getSearchHandler = () => filters.push(({ item }) => item.kind === mappedTypeFilter); } + if (tagFilter && tagFilter.length > 0) { + filters.push(({ item }) => + Boolean( + (item.kind === 'folder' || item.kind === 'dashboard') && item.tags?.some((tag) => tagFilter.includes(tag)) + ) + ); + } + if (folderFilter && folderFilter !== 'general') { filters.push( ({ item }) => (item.kind === 'folder' || item.kind === 'dashboard') && item.parentUID === folderFilter diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx index 8759dcef8f9..76485079afe 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.test.tsx @@ -1,96 +1,158 @@ +import { ComponentProps } from 'react'; +import { render, screen } from 'test/test-utils'; + +import { setBackendSrv } from '@grafana/runtime'; import { DashboardLink } from '@grafana/schema'; -import { backendSrv } from 'app/core/services/__mocks__/backend_srv'; +import { setupMockServer } from '@grafana/test-utils/server'; +import { getFolderFixtures } from '@grafana/test-utils/unstable'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { testWithFeatureToggles } from 'app/features/alerting/unified/test/test-utils'; import { LinkSrv } from 'app/features/panel/panellinks/link_srv'; +import { resetGrafanaSearcher } from 'app/features/search/service/searcher'; -import { DashboardSearchItem, DashboardSearchItemType } from '../../../search/types'; +import { resolveLinks, searchForTags, DashboardLinksDashboard } from './DashboardLinksDashboard'; -import { resolveLinks, searchForTags } from './DashboardLinksDashboard'; +const [_, { dashbdD }] = getFolderFixtures(); +setBackendSrv(backendSrv); +setupMockServer(); -describe('searchForTags', () => { - const setupTestContext = () => { - const tags = ['A', 'B']; - const link: DashboardLink = { - targetBlank: false, - keepTime: false, - includeVars: false, - asDropdown: false, - icon: 'some icon', - tags, - title: 'some title', - tooltip: 'some tooltip', - type: 'dashboards', - url: '/d/6ieouugGk/DashLinks', - }; - jest.spyOn(backendSrv, 'search').mockResolvedValue([]); - - return { link, backendSrv }; - }; - - describe('when called', () => { - it('then tags from link should be used in search and limit should be 100', async () => { - const { link, backendSrv } = setupTestContext(); - - const results = await searchForTags(link.tags, { getBackendSrv: () => backendSrv }); - - expect(results.length).toEqual(0); - expect(backendSrv.search).toHaveBeenCalledWith({ tag: ['A', 'B'], limit: 100 }); - expect(backendSrv.search).toHaveBeenCalledTimes(1); - }); - }); +afterEach(() => { + resetGrafanaSearcher(); }); -describe('resolveLinks', () => { - const setupTestContext = (dashboardUID: string, searchHitId: string) => { - const link: DashboardLink = { - targetBlank: false, - keepTime: false, - includeVars: false, - asDropdown: false, - icon: 'some icon', - tags: [], - title: 'some title', - tooltip: 'some tooltip', - type: 'dashboards', - url: '/d/6ieouugGk/DashLinks', - }; - const searchHits: DashboardSearchItem[] = [ - { - uid: searchHitId, - title: 'DashLinks', - url: '/d/6ieouugGk/DashLinks', - isStarred: false, +type DeepPartial = T extends object + ? { + [P in keyof T]?: DeepPartial; + } + : T; + +const dashboardUID = '1'; + +const baseLinkProps: ComponentProps['link'] = { + asDropdown: true, + icon: 'some icon', + includeVars: false, + keepTime: false, + tags: [], + targetBlank: false, + title: 'some title', + tooltip: '', + type: 'dashboards', +}; + +const getDashboardLink = () => screen.findByRole('link', { name: new RegExp(dashbdD.item.title) }); + +const renderComponent = (props: DeepPartial> = {}) => { + return render( + + ); +}; + +describe.each([ + // App platform APIs + true, + // Legacy APIs + false, +])('with unifiedStorageSearchUI: %s', (featureTogglesEnabled) => { + testWithFeatureToggles(featureTogglesEnabled ? ['unifiedStorageSearchUI'] : []); + + describe('DashboardLinksDashboard', () => { + it('renders a dropdown', async () => { + const { user } = renderComponent(); + const button = screen.getByRole('button', { name: /some title/i }); + await user.click(button); + expect(await screen.findByRole('menu')).toBeInTheDocument(); + expect(await getDashboardLink()).toBeInTheDocument(); + }); + + it('renders dropdown items with target _blank', async () => { + const { user } = renderComponent({ link: { targetBlank: true } }); + const button = screen.getByRole('button', { name: /some title/i }); + await user.click(button); + expect(await screen.findByRole('menu')).toBeInTheDocument(); + expect(await getDashboardLink()).toHaveAttribute('target', '_blank'); + }); + + it('handles an empty list of links', async () => { + const { user } = renderComponent({ link: { tags: ['foo-some-tag-of-which-there-are-none'] } }); + const button = screen.getByRole('button', { name: /some title/i }); + await user.click(button); + + expect(screen.getByRole('menuitem', { name: /no dashboards found/i })).toBeInTheDocument(); + }); + + it('renders a list of links', async () => { + renderComponent({ link: { asDropdown: false } }); + + const dashboardLink = await getDashboardLink(); + expect(dashboardLink).toBeInTheDocument(); + expect(dashboardLink).not.toHaveAttribute('target', '_blank'); + }); + + it('renders a list of links with target _blank', async () => { + renderComponent({ link: { asDropdown: false, targetBlank: true } }); + + const dashboardLink = await getDashboardLink(); + expect(dashboardLink).toHaveAttribute('target', '_blank'); + }); + + it('does not render a link to its own dashboard', async () => { + renderComponent({ link: { asDropdown: false }, dashboardUID: dashbdD.item.uid }); + + await screen.findAllByRole('link'); + expect(screen.queryByRole('link', { name: new RegExp(dashbdD.item.title) })).not.toBeInTheDocument(); + }); + }); + + describe('resolveLinks', () => { + const setupTestContext = () => { + const link: DashboardLink = { + targetBlank: false, + keepTime: false, + includeVars: false, + asDropdown: false, + icon: 'some icon', tags: [], - uri: 'db/DashLinks', - type: DashboardSearchItemType.DashDB, - }, - ]; - const linkSrv = { - getLinkUrl: jest.fn((args) => args.url), - } as unknown as LinkSrv; - const sanitize = jest.fn((args) => args); - const sanitizeUrl = jest.fn((args) => args); + title: 'some title', + tooltip: 'some tooltip', + type: 'dashboards', + url: '/d/6ieouugGk/DashLinks', + }; + const linkSrv = { + getLinkUrl: jest.fn((args) => args.url), + } as unknown as LinkSrv; + const sanitize = jest.fn((args) => args); + const sanitizeUrl = jest.fn((args) => args); - return { dashboardUID, link, searchHits, linkSrv, sanitize, sanitizeUrl }; - }; + return { link, linkSrv, sanitize, sanitizeUrl }; + }; - describe('when called', () => { - it('should filter out the calling dashboardUID', () => { - const { dashboardUID, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext('1', '1'); + it('should filter out the calling dashboardUID', async () => { + const { link, linkSrv, sanitize, sanitizeUrl } = setupTestContext(); + const { view: searchHits, totalRows } = await searchForTags([]); - const results = resolveLinks(dashboardUID, link, searchHits, { + const results = resolveLinks(dashbdD.item.uid, link, searchHits, { getLinkSrv: () => linkSrv, sanitize, sanitizeUrl, }); - expect(results.length).toEqual(0); - expect(linkSrv.getLinkUrl).toHaveBeenCalledTimes(0); - expect(sanitize).toHaveBeenCalledTimes(0); - expect(sanitizeUrl).toHaveBeenCalledTimes(0); + expect(results.find((result) => result.uid === dashbdD.item.uid)).toBeUndefined(); + + const expectedNumberOfResults = totalRows - 1; + expect(results.length).toEqual(expectedNumberOfResults); + expect(linkSrv.getLinkUrl).toHaveBeenCalledTimes(expectedNumberOfResults); + expect(sanitize).toHaveBeenCalledTimes(expectedNumberOfResults); + expect(sanitizeUrl).toHaveBeenCalledTimes(expectedNumberOfResults); }); - it('should resolve link url', () => { - const { dashboardUID, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext('1', '2'); + it('should resolve link url', async () => { + const { link, linkSrv, sanitize, sanitizeUrl } = setupTestContext(); + const { view: searchHits, totalRows } = await searchForTags([]); const results = resolveLinks(dashboardUID, link, searchHits, { getLinkSrv: () => linkSrv, @@ -98,13 +160,14 @@ describe('resolveLinks', () => { sanitizeUrl, }); - expect(results.length).toEqual(1); - expect(linkSrv.getLinkUrl).toHaveBeenCalledTimes(1); - expect(linkSrv.getLinkUrl).toHaveBeenCalledWith({ ...link, url: searchHits[0].url }); + expect(results.length).toBeGreaterThan(0); + expect(linkSrv.getLinkUrl).toHaveBeenCalledTimes(totalRows); + expect(linkSrv.getLinkUrl).toHaveBeenCalledWith({ ...link, url: searchHits.at(0)?.url }); }); - it('should sanitize title', () => { - const { dashboardUID, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext('1', '2'); + it('should sanitize title', async () => { + const { link, linkSrv, sanitize, sanitizeUrl } = setupTestContext(); + const { view: searchHits, totalRows } = await searchForTags([]); const results = resolveLinks(dashboardUID, link, searchHits, { getLinkSrv: () => linkSrv, @@ -112,23 +175,24 @@ describe('resolveLinks', () => { sanitizeUrl, }); - expect(results.length).toEqual(1); - expect(sanitize).toHaveBeenCalledTimes(1); - expect(sanitize).toHaveBeenCalledWith(searchHits[0].title); + expect(results.length).toBeGreaterThan(0); + expect(sanitize).toHaveBeenCalledTimes(totalRows); + expect(sanitize).toHaveBeenCalledWith(searchHits.at(0)?.name); }); - it('should sanitize url', () => { - const { dashboardUID, link, searchHits, linkSrv, sanitize, sanitizeUrl } = setupTestContext('1', '2'); - + it('should sanitize url', async () => { + const { link, linkSrv, sanitize, sanitizeUrl } = setupTestContext(); + const result = await searchForTags([]); + const { view: searchHits, totalRows } = result; const results = resolveLinks(dashboardUID, link, searchHits, { getLinkSrv: () => linkSrv, sanitize, sanitizeUrl, }); - expect(results.length).toEqual(1); - expect(sanitizeUrl).toHaveBeenCalledTimes(1); - expect(sanitizeUrl).toHaveBeenCalledWith(searchHits[0].url); + expect(results.length).toBeGreaterThan(0); + expect(sanitizeUrl).toHaveBeenCalledTimes(totalRows); + expect(sanitizeUrl).toHaveBeenCalledWith(searchHits.at(0)?.url); }); }); }); diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx index 7b7286ea618..af9f6702832 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx @@ -9,14 +9,14 @@ import { t } from '@grafana/i18n'; import { DashboardLink } from '@grafana/schema'; import { Dropdown, Icon, LinkButton, Button, Menu, ScrollContainer, useStyles2 } from '@grafana/ui'; import { ButtonLinkProps } from '@grafana/ui/internal'; -import { getBackendSrv } from 'app/core/services/backend_srv'; -import { DashboardSearchItem } from 'app/features/search/types'; +import { getGrafanaSearcher } from 'app/features/search/service/searcher'; +import { DashboardQueryResult } from 'app/features/search/service/types'; import { getLinkSrv } from '../../../panel/panellinks/link_srv'; interface Props { link: DashboardLink; - linkInfo: { title: string; href: string }; + linkInfo: { title: string }; dashboardUID: string; scopedVars?: ScopedVars; } @@ -30,8 +30,15 @@ function DashboardLinksMenu({ dashboardUID, link }: DashboardLinksMenuProps) { const styles = useStyles2(getStyles); const resolvedLinks = useResolvedLinks({ dashboardUID, link }); - if (!resolvedLinks || resolveLinks.length === 0) { - return null; + if (!resolvedLinks || resolvedLinks.length === 0) { + return ( + + + + ); } return ( @@ -60,9 +67,9 @@ function DashboardLinksMenu({ dashboardUID, link }: DashboardLinksMenuProps) { ); } -export const DashboardLinksDashboard = (props: Props) => { - const { link, linkInfo, dashboardUID } = props; - const resolvedLinks = useResolvedLinks(props); +export const DashboardLinksDashboard = ({ link, linkInfo, dashboardUID }: Props) => { + const { title } = linkInfo; + const resolvedLinks = useResolvedLinks({ link, dashboardUID }); const styles = useStyles2(getStyles); if (link.asDropdown) { @@ -78,7 +85,7 @@ export const DashboardLinksDashboard = (props: Props) => { data-testid={selectors.components.DashboardLinks.dropDown} > - {linkInfo.title} + {title} ); @@ -113,7 +120,7 @@ const useResolvedLinks = ({ link, dashboardUID }: Pick { - const limit = 100; - const searchHits: DashboardSearchItem[] = await dependencies.getBackendSrv().search({ tag: tags, limit }); - - return searchHits; +export async function searchForTags(tags: string[]) { + return getGrafanaSearcher().search({ limit: 100, tags, kind: ['dashboard'] }); } export function resolveLinks( dashboardUID: string, link: DashboardLink, - searchHits: DashboardSearchItem[], + searchHits: DashboardQueryResult[], dependencies: { getLinkSrv: typeof getLinkSrv; sanitize: typeof sanitize; sanitizeUrl: typeof sanitizeUrl } = { getLinkSrv, sanitize, sanitizeUrl, } ): ResolvedLinkDTO[] { - return searchHits - .filter((searchHit) => searchHit.uid !== dashboardUID) - .map((searchHit) => { - const uid = searchHit.uid; - const title = dependencies.sanitize(searchHit.title); - const resolvedLink = dependencies.getLinkSrv().getLinkUrl({ ...link, url: searchHit.url }); - const url = dependencies.sanitizeUrl(resolvedLink); - - return { uid, title, url }; - }); + const hits: ResolvedLinkDTO[] = []; + for (const searchHit of searchHits) { + if (searchHit.uid === dashboardUID) { + continue; + } + const uid = searchHit.uid; + const title = dependencies.sanitize(searchHit.name); + const resolvedLink = dependencies.getLinkSrv().getLinkUrl({ ...link, url: searchHit.url }); + const url = dependencies.sanitizeUrl(resolvedLink); + hits.push({ uid, title, url }); + } + return hits; } function getStyles(theme: GrafanaTheme2) { diff --git a/public/app/features/search/service/searcher.ts b/public/app/features/search/service/searcher.ts index 2710a789050..9ea970c7156 100644 --- a/public/app/features/search/service/searcher.ts +++ b/public/app/features/search/service/searcher.ts @@ -23,3 +23,14 @@ export function getGrafanaSearcher(): GrafanaSearcher { } return searcher!; } + +/** + * Testing only - otherwise tests will use the same searcher instance, making it hard to test unified search vs legacy + * @deprecated Don't use this other than in tests! + */ +export function resetGrafanaSearcher() { + if (process.env.NODE_ENV !== 'test') { + throw new Error('resetGrafanaSearcher can only be used in tests'); + } + searcher = undefined; +} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 1556f5b49eb..f3afe2eb828 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -4607,7 +4607,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "{{dashboardName}} dashboard" + "aria-label-dashboard-name": "{{dashboardName}} dashboard", + "label-no-dashboards-found": "No dashboards found" }, "dashboard-loading": { "cancel-loading-dashboard": "Cancel loading dashboard" From 86edf28082afab3345ab17d28ef6b0ae1fab7cb5 Mon Sep 17 00:00:00 2001 From: Andre Pereira Date: Tue, 14 Oct 2025 13:06:21 +0100 Subject: [PATCH 02/75] Node graph: Fix context menu position after scrolling (#112374) Use scrollY in calculation for node context menu top value --- public/app/plugins/panel/nodeGraph/useContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/nodeGraph/useContextMenu.tsx b/public/app/plugins/panel/nodeGraph/useContextMenu.tsx index 46ea1355703..65daef5aeda 100644 --- a/public/app/plugins/panel/nodeGraph/useContextMenu.tsx +++ b/public/app/plugins/panel/nodeGraph/useContextMenu.tsx @@ -83,7 +83,7 @@ function makeContextMenu( renderMenuItems={renderer} onClose={() => setMenu(undefined)} x={event.pageX} - y={event.pageY} + y={event.pageY - window.scrollY} /> ); } From 8a827b5b05258edb58cfb535c3ad5e95c8c7d79f Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Tue, 14 Oct 2025 14:58:16 +0200 Subject: [PATCH 03/75] Alerting: Triage rule details drawer (#112055) * Add basic drawers for rules and instances * Add query visualization for instances * Display threshold on graphs when available * Add basic history state transitions * Query and annotations chart for alert instance * Use SceneDataNode to merge query and annotations * Split drawer components into more files * move the drawer to the workbench so we can persist its state with pagination updates the drawer contents to align closer to what we have for the detail view * Don't collapse summary on empty values * Improve data loading in InstanceDetailsDrawer * Refactor history data conversion * Tidy up state history data conversion * Replace rule name link with a dedicated drawer button * remove filter text * Improve history time series handling * Improve rule details header * Update translations * Use custom filter function for instance series filtering * Fix instances matching algorithm * make very long rule names span multiple rows --------- Co-authored-by: Gilles De Mey --- .../central-state-history/EventDetails.tsx | 18 +- .../useRuleHistoryRecords.ts | 27 +-- .../rules/central-state-history/utils.ts | 19 +- .../rules/state-history/common.test.ts | 146 +++++++++++- .../components/rules/state-history/common.ts | 35 +++ .../state-history/useRuleHistoryRecords.tsx | 25 +- .../alerting/unified/triage/Workbench.tsx | 1 + .../InstanceDetailsDrawer.tsx | 213 ++++++++++++++++++ .../instance-details/QueryVisualization.tsx | 113 ++++++++++ .../instance-details/stateHistoryUtils.ts | 50 ++++ .../unified/triage/rows/AlertRuleRow.tsx | 79 ++++--- .../unified/triage/rows/InstanceRow.tsx | 89 ++++++-- .../triage/rule-details/RuleDetailsDrawer.tsx | 149 ++++++++++++ .../triage/scene/AlertRuleInstances.tsx | 1 + .../unified/triage/scene/SummaryStats.tsx | 4 +- public/locales/en-US/grafana.json | 30 ++- 16 files changed, 861 insertions(+), 138 deletions(-) create mode 100644 public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx create mode 100644 public/app/features/alerting/unified/triage/instance-details/QueryVisualization.tsx create mode 100644 public/app/features/alerting/unified/triage/instance-details/stateHistoryUtils.ts create mode 100644 public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/EventDetails.tsx b/public/app/features/alerting/unified/components/rules/central-state-history/EventDetails.tsx index 2711e59a5a7..5a998199eba 100644 --- a/public/app/features/alerting/unified/components/rules/central-state-history/EventDetails.tsx +++ b/public/app/features/alerting/unified/components/rules/central-state-history/EventDetails.tsx @@ -19,8 +19,7 @@ import { AnnotationValue } from '../../rule-viewer/tabs/Details'; import { ErrorMessageRow } from '../state-history/ErrorMessageRow'; import { LogTimelineViewer } from '../state-history/LogTimelineViewer'; import { useFrameSubset } from '../state-history/LokiStateHistory'; -import { LogRecord } from '../state-history/common'; -import { isLine, isNumbers } from '../state-history/useRuleHistoryRecords'; +import { LogRecord, historyDataFrameToLogRecords } from '../state-history/common'; import { EventState, FilterType, LIMIT_EVENTS } from './EventListSceneObject'; import { HistoryErrorMessage } from './HistoryErrorMessage'; @@ -87,20 +86,7 @@ function useRuleHistoryRecordsForTheInstance(labelsForTheInstance: string, state const theme = useTheme2(); return useMemo(() => { - // merge timestamp with "line" - const tsValues = stateHistory?.data?.values[0] ?? []; - const timestamps: number[] = isNumbers(tsValues) ? tsValues : []; - const lines = stateHistory?.data?.values[1] ?? []; - - const logRecords = timestamps.reduce((acc: LogRecord[], timestamp: number, index: number) => { - const line = lines[index]; - // values property can be undefined for some instance states (e.g. NoData) - if (isLine(line)) { - acc.push({ timestamp, line }); - } - - return acc; - }, []); + const logRecords = historyDataFrameToLogRecords(stateHistory); // group all records by alert instance (unique set of labels) const logRecordsByInstance = groupBy(logRecords, (record: LogRecord) => { diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/useRuleHistoryRecords.ts b/public/app/features/alerting/unified/components/rules/central-state-history/useRuleHistoryRecords.ts index b56b2a81f24..afa3989cd8a 100644 --- a/public/app/features/alerting/unified/components/rules/central-state-history/useRuleHistoryRecords.ts +++ b/public/app/features/alerting/unified/components/rules/central-state-history/useRuleHistoryRecords.ts @@ -5,8 +5,7 @@ import { mapStateWithReasonToBaseState } from 'app/types/unified-alerting-dto'; import { labelsMatchMatchers } from '../../../utils/alertmanager'; import { parsePromQLStyleMatcherLooseSafe } from '../../../utils/matchers'; -import { LogRecord } from '../state-history/common'; -import { isLine, isNumbers } from '../state-history/useRuleHistoryRecords'; +import { historyDataFrameToLogRecords } from '../state-history/common'; import { StateFilterValues } from './constants'; @@ -39,35 +38,25 @@ export function useRuleHistoryRecords(stateHistory?: DataFrameJSON, filters: His export function ruleHistoryToRecords(stateHistory?: DataFrameJSON, filters: HistoryRecordFilters = emptyFilters) { const { labels, stateFrom = StateFilterValues.all, stateTo = StateFilterValues.all } = filters; - if (!stateHistory?.data) { + const allLogRecords = historyDataFrameToLogRecords(stateHistory); + + if (allLogRecords.length === 0) { return { historyRecords: [] }; } const filterMatchers = labels ? parsePromQLStyleMatcherLooseSafe(labels) : []; - const [tsValues, lines] = stateHistory.data.values; - const timestamps = isNumbers(tsValues) ? tsValues : []; - - // merge timestamp with "line" - const logRecords = timestamps.reduce((acc: LogRecord[], timestamp: number, index: number) => { - const line = lines[index]; - if (!isLine(line)) { - return acc; - } + const filteredRecords = allLogRecords.filter(({ line }) => { // values property can be undefined for some instance states (e.g. NoData) const filterMatch = line.labels && labelsMatchMatchers(line.labels, filterMatchers); const baseStateTo = mapStateWithReasonToBaseState(line.current); const baseStateFrom = mapStateWithReasonToBaseState(line.previous); const stateToMatch = stateTo !== StateFilterValues.all ? stateTo === baseStateTo : true; const stateFromMatch = stateFrom !== StateFilterValues.all ? stateFrom === baseStateFrom : true; - if (filterMatch && stateToMatch && stateFromMatch) { - acc.push({ timestamp, line }); - } - - return acc; - }, []); + return filterMatch && stateToMatch && stateFromMatch; + }); return { - historyRecords: logRecords, + historyRecords: filteredRecords, }; } diff --git a/public/app/features/alerting/unified/components/rules/central-state-history/utils.ts b/public/app/features/alerting/unified/components/rules/central-state-history/utils.ts index 968253301a5..e21f7c98e54 100644 --- a/public/app/features/alerting/unified/components/rules/central-state-history/utils.ts +++ b/public/app/features/alerting/unified/components/rules/central-state-history/utils.ts @@ -15,8 +15,7 @@ import { fieldIndexComparer } from '@grafana/data/internal'; import { labelsMatchMatchers } from '../../../utils/alertmanager'; import { parsePromQLStyleMatcherLooseSafe } from '../../../utils/matchers'; -import { LogRecord } from '../state-history/common'; -import { isLine, isNumbers } from '../state-history/useRuleHistoryRecords'; +import { LogRecord, historyDataFrameToLogRecords } from '../state-history/common'; import { LABELS_FILTER, STATE_FILTER_FROM, STATE_FILTER_TO } from './CentralAlertHistoryScene'; import { StateFilterValues } from './constants'; @@ -55,20 +54,8 @@ const emptyFilters: HistoryFilters = { * We group all records by alert instance (unique set of labels) and create a DataFrame for each group (instance). * This allows us to be able to filter by labels and states in the groupDataFramesByTime function. */ -export function historyResultToDataFrame({ data }: DataFrameJSON, filters = emptyFilters): DataFrame[] { - // Extract timestamps and lines from the response - const [tsValues = [], lines = []] = data?.values ?? []; - const timestamps = isNumbers(tsValues) ? tsValues : []; - - const logRecords = timestamps.reduce((acc, timestamp: number, index: number) => { - const line = lines[index]; - if (!isLine(line)) { - return acc; - } - - acc.push({ timestamp, line }); - return acc; - }, []); +export function historyResultToDataFrame(stateHistory: DataFrameJSON, filters = emptyFilters): DataFrame[] { + const logRecords = historyDataFrameToLogRecords(stateHistory); // Group log records by alert instance const logRecordsByInstance = groupBy(logRecords, (record: LogRecord) => { diff --git a/public/app/features/alerting/unified/components/rules/state-history/common.test.ts b/public/app/features/alerting/unified/components/rules/state-history/common.test.ts index 947098e60ec..96b4bea0f25 100644 --- a/public/app/features/alerting/unified/components/rules/state-history/common.test.ts +++ b/public/app/features/alerting/unified/components/rules/state-history/common.test.ts @@ -1,4 +1,6 @@ -import { Label, extractCommonLabels, omitLabels } from './common'; +import { DataFrameJSON } from '@grafana/data'; + +import { Label, extractCommonLabels, historyDataFrameToLogRecords, omitLabels } from './common'; test('extractCommonLabels', () => { const labels: Label[][] = [ @@ -48,3 +50,145 @@ test('omitLabels with no common labels', () => { expect(omitLabels(labels, commonLabels)).toStrictEqual(labels); }); + +describe('historyDataFrameToLogRecords', () => { + test('should return empty array when stateHistory is undefined', () => { + expect(historyDataFrameToLogRecords(undefined)).toEqual([]); + }); + + test('should return empty array when stateHistory.data is undefined', () => { + const stateHistory: DataFrameJSON = {} as DataFrameJSON; + expect(historyDataFrameToLogRecords(stateHistory)).toEqual([]); + }); + + test('should return empty array when stateHistory.data.values is empty', () => { + const stateHistory: DataFrameJSON = { + data: { values: [] }, + schema: { fields: [] }, + }; + expect(historyDataFrameToLogRecords(stateHistory)).toEqual([]); + }); + + test('should convert valid state history to log records', () => { + const stateHistory: DataFrameJSON = { + data: { + values: [ + [1681739580000, 1681739590000, 1681739600000], + [ + { + previous: 'Normal', + current: 'Alerting', + values: { B: 1 }, + labels: { alertname: 'test-rule', grafana_folder: 'folder-one' }, + }, + { + previous: 'Alerting', + current: 'Normal', + values: { B: 0 }, + labels: { alertname: 'test-rule', grafana_folder: 'folder-one' }, + }, + { + previous: 'Normal', + current: 'Pending', + values: { B: 0.5 }, + labels: { alertname: 'test-rule', grafana_folder: 'folder-one' }, + }, + ], + ], + }, + schema: { fields: [] }, + }; + + const result = historyDataFrameToLogRecords(stateHistory); + + expect(result).toHaveLength(3); + expect(result[0]).toEqual({ + timestamp: 1681739580000, + line: { + previous: 'Normal', + current: 'Alerting', + values: { B: 1 }, + labels: { alertname: 'test-rule', grafana_folder: 'folder-one' }, + }, + }); + expect(result[1]).toEqual({ + timestamp: 1681739590000, + line: { + previous: 'Alerting', + current: 'Normal', + values: { B: 0 }, + labels: { alertname: 'test-rule', grafana_folder: 'folder-one' }, + }, + }); + expect(result[2]).toEqual({ + timestamp: 1681739600000, + line: { + previous: 'Normal', + current: 'Pending', + values: { B: 0.5 }, + labels: { alertname: 'test-rule', grafana_folder: 'folder-one' }, + }, + }); + }); + + test('should skip invalid line objects that are missing current or previous', () => { + const stateHistory: DataFrameJSON = { + data: { + values: [ + [1681739580000, 1681739590000, 1681739600000], + [ + { + previous: 'Normal', + current: 'Alerting', + values: { B: 1 }, + labels: { alertname: 'test' }, + }, + { + // Missing 'current' field - invalid + previous: 'Alerting', + values: { B: 0 }, + labels: { alertname: 'test' }, + }, + { + previous: 'Normal', + current: 'Pending', + values: { B: 0.5 }, + labels: { alertname: 'test' }, + }, + ], + ], + }, + schema: { fields: [] }, + }; + + const result = historyDataFrameToLogRecords(stateHistory); + + expect(result).toHaveLength(2); + expect(result[0].timestamp).toBe(1681739580000); + expect(result[1].timestamp).toBe(1681739600000); + }); + + test('should handle lines with empty values', () => { + const stateHistory: DataFrameJSON = { + data: { + values: [ + [1681739580000], + [ + { + previous: 'Alerting', + current: 'Normal', + values: {}, + labels: { alertname: 'test' }, + }, + ], + ], + }, + schema: { fields: [] }, + }; + + const result = historyDataFrameToLogRecords(stateHistory); + + expect(result).toHaveLength(1); + expect(result[0].line.values).toEqual({}); + }); +}); diff --git a/public/app/features/alerting/unified/components/rules/state-history/common.ts b/public/app/features/alerting/unified/components/rules/state-history/common.ts index 4076dde37ea..3765863936b 100644 --- a/public/app/features/alerting/unified/components/rules/state-history/common.ts +++ b/public/app/features/alerting/unified/components/rules/state-history/common.ts @@ -1,5 +1,6 @@ import { isEqual, uniqBy } from 'lodash'; +import { DataFrameJSON } from '@grafana/data'; import { GrafanaAlertStateWithReason } from 'app/types/unified-alerting-dto'; export interface Line { @@ -40,3 +41,37 @@ export function extractCommonLabels(labels: Label[][]): Label[] { return commonLabels; } + +export function historyDataFrameToLogRecords(stateHistory?: DataFrameJSON): LogRecord[] { + if (!stateHistory?.data || !stateHistory.data.values || !Array.isArray(stateHistory.data.values)) { + return []; + } + + const [tsValues, lines] = stateHistory.data.values; + + if (!Array.isArray(tsValues) || !Array.isArray(lines) || tsValues.length !== lines.length) { + return []; + } + + const timestamps = isNumbers(tsValues) ? tsValues : []; + + // merge timestamp with "line" + const logRecords = timestamps.reduce((acc: LogRecord[], timestamp: number, index: number) => { + const line = lines[index]; + if (!isLine(line)) { + return acc; + } + acc.push({ timestamp, line }); + return acc; + }, []); + + return logRecords; +} + +export function isNumbers(value: unknown[]): value is number[] { + return value.every((v) => typeof v === 'number'); +} + +export function isLine(value: unknown): value is Line { + return typeof value === 'object' && value !== null && 'current' in value && 'previous' in value; +} diff --git a/public/app/features/alerting/unified/components/rules/state-history/useRuleHistoryRecords.tsx b/public/app/features/alerting/unified/components/rules/state-history/useRuleHistoryRecords.tsx index 90d989fdcfc..a5586862e56 100644 --- a/public/app/features/alerting/unified/components/rules/state-history/useRuleHistoryRecords.tsx +++ b/public/app/features/alerting/unified/components/rules/state-history/useRuleHistoryRecords.tsx @@ -16,26 +16,13 @@ import { useTheme2 } from '@grafana/ui'; import { labelsMatchMatchers } from '../../../utils/alertmanager'; import { parsePromQLStyleMatcherLooseSafe } from '../../../utils/matchers'; -import { Line, LogRecord, extractCommonLabels, omitLabels } from './common'; +import { LogRecord, extractCommonLabels, historyDataFrameToLogRecords, omitLabels } from './common'; export function useRuleHistoryRecords(stateHistory?: DataFrameJSON, filter?: string) { const theme = useTheme2(); return useMemo(() => { - // merge timestamp with "line" - const tsValues = stateHistory?.data?.values[0] ?? []; - const timestamps: number[] = isNumbers(tsValues) ? tsValues : []; - const lines = stateHistory?.data?.values[1] ?? []; - - const logRecords = timestamps.reduce((acc: LogRecord[], timestamp: number, index: number) => { - const line = lines[index]; - // values property can be undefined for some instance states (e.g. NoData) - if (isLine(line)) { - acc.push({ timestamp, line }); - } - - return acc; - }, []); + const logRecords = historyDataFrameToLogRecords(stateHistory); // group all records by alert instance (unique set of labels) const logRecordsByInstance = groupBy(logRecords, (record: LogRecord) => { @@ -70,14 +57,6 @@ export function useRuleHistoryRecords(stateHistory?: DataFrameJSON, filter?: str }, [stateHistory, filter, theme]); } -export function isNumbers(value: unknown[]): value is number[] { - return value.every((v) => typeof v === 'number'); -} - -export function isLine(value: unknown): value is Line { - return typeof value === 'object' && value !== null && 'current' in value && 'previous' in value; -} - // Each alert instance is represented by a data frame // Each frame consists of two fields: timestamp and state change export function logRecordsToDataFrame( diff --git a/public/app/features/alerting/unified/triage/Workbench.tsx b/public/app/features/alerting/unified/triage/Workbench.tsx index 98c07cc8de6..85a4788a706 100644 --- a/public/app/features/alerting/unified/triage/Workbench.tsx +++ b/public/app/features/alerting/unified/triage/Workbench.tsx @@ -192,6 +192,7 @@ export const getStyles = (theme: GrafanaTheme2) => { virtualizedContainer: css({ display: 'flex', flex: 1, + wordBreak: 'break-all', // make very long rule names render higher rows overflow: 'hidden', // Let AutoSizer handle the overflow }), summaryContainer: css({ diff --git a/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx new file mode 100644 index 00000000000..704c4d54c47 --- /dev/null +++ b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx @@ -0,0 +1,213 @@ +import { css } from '@emotion/css'; +import { orderBy } from 'lodash'; +import { Fragment, useMemo } from 'react'; +import { useMeasure } from 'react-use'; + +import { AlertLabels } from '@grafana/alerting/unstable'; +import { GrafanaTheme2, Labels } from '@grafana/data'; +import { t } from '@grafana/i18n'; +import { isFetchError } from '@grafana/runtime'; +import { TimeRangePicker, useTimeRange } from '@grafana/scenes-react'; +import { Alert, Box, Drawer, Icon, LoadingBar, Stack, Text, useStyles2 } from '@grafana/ui'; +import { AlertQuery, GrafanaRuleDefinition } from 'app/types/unified-alerting-dto'; + +import { alertRuleApi } from '../../api/alertRuleApi'; +import { stateHistoryApi } from '../../api/stateHistoryApi'; +import { getThresholdsForQueries } from '../../components/rule-editor/util'; +import { EventState } from '../../components/rules/central-state-history/EventListSceneObject'; +import { LogRecord, historyDataFrameToLogRecords } from '../../components/rules/state-history/common'; +import { isAlertQueryOfAlertData } from '../../rule-editor/formProcessing'; +import { stringifyErrorLike } from '../../utils/misc'; + +import { QueryVisualization } from './QueryVisualization'; +import { convertStateHistoryToAnnotations } from './stateHistoryUtils'; + +const { useGetAlertRuleQuery } = alertRuleApi; +const { useGetRuleHistoryQuery } = stateHistoryApi; + +interface InstanceDetailsDrawerProps { + ruleUID: string; + instanceLabels: Labels; + onClose: () => void; +} + +export function InstanceDetailsDrawer({ ruleUID, instanceLabels, onClose }: InstanceDetailsDrawerProps) { + const [ref, { width: loadingBarWidth }] = useMeasure(); + const [timeRange] = useTimeRange(); + + const { data: rule, isLoading: loading, error } = useGetAlertRuleQuery({ uid: ruleUID }); + + const { dataQueries, thresholds } = useMemo(() => { + if (rule) { + return extractQueryDetails(rule.grafana_alert); + } + return { dataQueries: [], thresholds: {} }; + }, [rule]); + + // Fetch state history for this specific instance + const { + data: stateHistoryData, + isFetching: stateHistoryFetching, + isError: stateHistoryError, + } = useGetRuleHistoryQuery({ + ruleUid: ruleUID, + labels: instanceLabels, + from: timeRange.from.unix(), + to: timeRange.to.unix(), + }); + + // Convert state history to LogRecords and filter by instance labels + const { historyRecords, annotations } = useMemo(() => { + const historyRecords = historyDataFrameToLogRecords(stateHistoryData); + const annotations = convertStateHistoryToAnnotations(historyRecords); + + return { historyRecords, annotations }; + }, [stateHistoryData]); + + if (error) { + return ( + + + + ); + } + + if (loading || !rule) { + return ( + +
{t('alerting.common.loading', 'Loading...')}
+
+ ); + } + + return ( + + + + + + {dataQueries.length > 0 && ( + + + {dataQueries.map((query, index) => ( + + ))} + + + )} + + + + + + + {t('alerting.instance-details.state-history', 'Recent State Changes')} + {stateHistoryFetching && } + {stateHistoryError && ( + + {t( + 'alerting.instance-details.history-error-desc', + 'Unable to fetch state transition history for this instance.' + )} + + )} + {!stateHistoryFetching && !stateHistoryError && ( + + {historyRecords.length > 0 ? ( + + ) : ( + {t('alerting.instance-details.no-history', 'No recent state changes')} + )} + + )} + + + + ); +} + +function extractQueryDetails(rule: GrafanaRuleDefinition) { + const dataQueries = rule.data.filter((query: AlertQuery) => isAlertQueryOfAlertData(query)); + + const allQueries = rule.data; + const condition = rule.condition; + + const thresholds = getThresholdsForQueries(allQueries, condition); + + return { dataQueries, thresholds }; +} + +const dateFormatter = new Intl.DateTimeFormat(undefined, { + month: 'short', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', +}); + +function formatTimestamp(timestamp: number) { + return dateFormatter.format(new Date(timestamp)); +} + +function InstanceStateTransitions({ records }: { records: LogRecord[] }) { + const styles = useStyles2(stateTransitionStyles); + const sortedRecords = orderBy(records, (r) => r.timestamp, 'desc'); + + return ( +
+ {sortedRecords.map((record, index) => ( + + + {formatTimestamp(record.timestamp)} + + {}} type="from" /> + + {}} type="to" /> + + ))} +
+ ); +} + +const stateTransitionStyles = (theme: GrafanaTheme2) => ({ + container: css({ + display: 'grid', + gridTemplateColumns: 'max-content max-content max-content max-content', + gap: theme.spacing(1, 2), + alignItems: 'center', + padding: theme.spacing(1, 0), + }), +}); + +interface ErrorContentProps { + error: unknown; +} + +function ErrorContent({ error }: ErrorContentProps) { + if (isFetchError(error) && error.status === 404) { + return ( + + {t('alerting.triage.rule-not-found.description', 'The requested rule could not be found.')} + + ); + } + + return ( + + {stringifyErrorLike(error)} + + ); +} diff --git a/public/app/features/alerting/unified/triage/instance-details/QueryVisualization.tsx b/public/app/features/alerting/unified/triage/instance-details/QueryVisualization.tsx new file mode 100644 index 00000000000..65f25894c34 --- /dev/null +++ b/public/app/features/alerting/unified/triage/instance-details/QueryVisualization.tsx @@ -0,0 +1,113 @@ +import { useMemo } from 'react'; + +import { DataFrame, Labels, LoadingState } from '@grafana/data'; +import { SceneDataNode, VizConfigBuilders } from '@grafana/scenes'; +import { VizPanel, useQueryRunner, useTimeRange } from '@grafana/scenes-react'; +import { GraphDrawStyle, LegendDisplayMode, TooltipDisplayMode, VisibilityMode } from '@grafana/schema'; +import { Box } from '@grafana/ui'; +import { AlertQuery } from 'app/types/unified-alerting-dto'; + +import { getThresholdsForQueries } from '../../components/rule-editor/util'; + +interface QueryVisualizationProps { + query: AlertQuery; + instanceLabels: Labels; + thresholds?: ReturnType; + annotations?: DataFrame[]; +} + +export function QueryVisualization({ query, instanceLabels, thresholds, annotations = [] }: QueryVisualizationProps) { + const [timeRange] = useTimeRange(); + // Convert query to range query for visualization + const visualizationQuery = useMemo(() => { + const model = { ...query.model, refId: query.refId }; + + // For Prometheus queries, ensure we use range queries for better visualization + if ('instant' in model) { + model.instant = false; + model.range = true; + } + + return model; + }, [query.model, query.refId]); + + // Create the base data provider + const baseDataProvider = useQueryRunner({ + datasource: { uid: query.datasourceUid }, + queries: [visualizationQuery], + }); + + // Get data and filter by instance labels + const { data } = baseDataProvider.useState(); + + // Filter frames to only include those matching the instance labels + const filteredSeries = useMemo(() => { + if (!data?.series || Object.keys(instanceLabels).length === 0) { + return data?.series || []; + } + + return data.series.filter((frame) => frameMatchesInstanceLabels(frame, instanceLabels)); + }, [data?.series, instanceLabels]); + + // Create a data provider with filtered series + const dataProvider = new SceneDataNode({ + data: { + series: filteredSeries, + state: data?.state || LoadingState.NotStarted, + timeRange: timeRange, + annotations: annotations, + }, + }); + + // Create visualization config with thresholds if available + const vizConfig = useMemo(() => { + const baseConfig = VizConfigBuilders.timeseries() + .setCustomFieldConfig('drawStyle', GraphDrawStyle.Line) + .setCustomFieldConfig('showPoints', VisibilityMode.Auto) + .setOption('tooltip', { mode: TooltipDisplayMode.Multi }) + .setOption('legend', { showLegend: false, displayMode: LegendDisplayMode.Hidden }); + + // Apply thresholds if available for this query + const queryThresholds = thresholds?.[query.refId]; + if (queryThresholds) { + baseConfig + .setThresholds(queryThresholds.config) + .setCustomFieldConfig('thresholdsStyle', { mode: queryThresholds.mode }); + } + + return baseConfig.build(); + }, [query.refId, thresholds]); + + return ( + + + + ); +} + +// Helper function to check if frame labels match instance labels +function frameMatchesInstanceLabels(frame: DataFrame, instanceLabels: Labels): boolean { + // Check if any field in the frame has labels that are a subset of instance labels + // (i.e., all field labels exist in instanceLabels with matching values) + // + // Note: We check if field.labels ⊆ instanceLabels (subset) rather than equality because + // the alert rule evaluation engine might apply additional rule-specific labels coming from + // rule labels and labels templating. Currently, we have no way of knowing these additional + // labels when querying the datasource directly, so instanceLabels may contain more labels + // than what appears in the raw query results. + for (const field of frame.fields) { + if (field.labels) { + const allFieldLabelsMatch = Object.entries(field.labels).every(([key, value]) => instanceLabels[key] === value); + if (allFieldLabelsMatch) { + return true; + } + } + } + return false; +} diff --git a/public/app/features/alerting/unified/triage/instance-details/stateHistoryUtils.ts b/public/app/features/alerting/unified/triage/instance-details/stateHistoryUtils.ts new file mode 100644 index 00000000000..8ef8457991e --- /dev/null +++ b/public/app/features/alerting/unified/triage/instance-details/stateHistoryUtils.ts @@ -0,0 +1,50 @@ +import { AnnotationEvent, DataFrame, DataTopic, arrayToDataFrame } from '@grafana/data'; + +import { LogRecord } from '../../components/rules/state-history/common'; + +// Function to get color based on alert state - matching EventState component colors +function getStateColor(state: string): string { + const stateStr = String(state).toLowerCase(); + if (stateStr.includes('normal')) { + return '#73BF69'; // Green + } else if (stateStr.includes('alerting')) { + return '#F2495C'; // Red + } else if (stateStr.includes('pending')) { + return '#FF9830'; // Orange + } else if (stateStr.includes('recovering')) { + return '#FF9830'; // Orange + } else if (stateStr.includes('nodata')) { + return '#5794F2'; // Blue + } + return '#8e8e8e'; // Gray for unknown states +} + +/** + * Converts log records to annotation DataFrames + * @param logRecords Array of LogRecord objects + * @returns Array of annotation DataFrames (empty array if no data) + */ +export function convertStateHistoryToAnnotations(logRecords: LogRecord[]): DataFrame[] { + if (!logRecords || logRecords.length === 0) { + return []; + } + + const annotationEvents: AnnotationEvent[] = logRecords.map((record) => { + const { timestamp, line } = record; + return { + time: timestamp, + title: `${line.previous} → ${line.current}`, + text: `State changed from ${line.previous} to ${line.current}`, + tags: ['state-transition'], + color: getStateColor(line.current), + }; + }); + + if (annotationEvents.length === 0) { + return []; + } + + const annotationFrame = arrayToDataFrame(annotationEvents); + annotationFrame.meta = { dataTopic: DataTopic.Annotations }; + return [annotationFrame]; +} diff --git a/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx b/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx index fefd37258f9..539dc2c4ed1 100644 --- a/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx @@ -1,10 +1,10 @@ -import React from 'react'; +import { useState } from 'react'; -import { Stack, Text, TextLink } from '@grafana/ui'; +import { t } from '@grafana/i18n'; +import { IconButton, Stack, Text } from '@grafana/ui'; import { MetaText } from '../../components/MetaText'; -import { WithReturnButton } from '../../components/WithReturnButton'; -import { rulesNav } from '../../utils/navigation'; +import { RuleDetailsDrawer } from '../rule-details/RuleDetailsDrawer'; import { AlertRuleInstances } from '../scene/AlertRuleInstances'; import { AlertRuleSummary } from '../scene/AlertRuleSummary'; import { AlertRuleRow as AlertRuleRowType } from '../types'; @@ -19,37 +19,46 @@ interface AlertRuleRowProps { } export const AlertRuleRow = ({ row, leftColumnWidth, rowKey, depth = 0 }: AlertRuleRowProps) => { + const { ruleUID, folder, title } = row.metadata; + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + + const handleDrawerOpen = () => { + setIsDrawerOpen(true); + }; + + const handleDrawerClose = () => { + setIsDrawerOpen(false); + }; + return ( - - {row.metadata.title} - - } - /> - } - metadata={ - - - - {row.metadata.folder} - - - } - content={} - depth={depth} - > - - + <> + {title}} + actions={ + + } + metadata={ + + + + {folder} + + + } + content={} + depth={depth} + > + + + + {isDrawerOpen && } + ); }; diff --git a/public/app/features/alerting/unified/triage/rows/InstanceRow.tsx b/public/app/features/alerting/unified/triage/rows/InstanceRow.tsx index 7480d6f8a7e..e18898aa48e 100644 --- a/public/app/features/alerting/unified/triage/rows/InstanceRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/InstanceRow.tsx @@ -1,16 +1,17 @@ import { css } from '@emotion/css'; import { isEmpty } from 'lodash'; -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import { AlertLabels } from '@grafana/alerting/unstable'; import { DataFrame, GrafanaTheme2, Labels, LoadingState, TimeRange } from '@grafana/data'; -import { Trans } from '@grafana/i18n'; +import { Trans, t } from '@grafana/i18n'; import { SceneDataNode, VizConfigBuilders } from '@grafana/scenes'; import { VizPanel } from '@grafana/scenes-react'; import { GraphDrawStyle, VisibilityMode } from '@grafana/schema'; import { AxisPlacement, BarAlignment, + IconButton, LegendDisplayMode, StackingMode, Text, @@ -19,6 +20,7 @@ import { } from '@grafana/ui'; import { overrideToFixedColor } from '../../home/Insights'; +import { InstanceDetailsDrawer } from '../instance-details/InstanceDetailsDrawer'; import { GenericRow } from './GenericRow'; @@ -32,6 +34,7 @@ interface InstanceRowProps { commonLabels: Labels; leftColumnWidth: number; timeRange: TimeRange; + ruleUID: string; depth?: number; } @@ -61,8 +64,24 @@ const chartConfig = VizConfigBuilders.timeseries() ) .build(); -export function InstanceRow({ instance, commonLabels, leftColumnWidth, timeRange, depth = 0 }: InstanceRowProps) { +export function InstanceRow({ + instance, + commonLabels, + leftColumnWidth, + timeRange, + ruleUID, + depth = 0, +}: InstanceRowProps) { const styles = useStyles2(getStyles); + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + + const handleDrawerOpen = () => { + setIsDrawerOpen(true); + }; + + const handleDrawerClose = () => { + setIsDrawerOpen(false); + }; const dataProvider = useMemo( () => @@ -77,29 +96,49 @@ export function InstanceRow({ instance, commonLabels, leftColumnWidth, timeRange ); return ( - - - No labels - - - ) : ( - + + + No labels + + + ) : ( + + ) + } + actions={ + - ) - } - content={ - - } - depth={depth} - /> + } + content={ + + } + depth={depth} + /> + + {isDrawerOpen && ( + + )} + ); } diff --git a/public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx b/public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx new file mode 100644 index 00000000000..69687456341 --- /dev/null +++ b/public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx @@ -0,0 +1,149 @@ +import { useMemo, useState } from 'react'; + +import { Trans, t } from '@grafana/i18n'; +import { isFetchError } from '@grafana/runtime'; +import { Alert, Box, Drawer, LinkButton, Stack, Tab, TabContent, TabsBar, Text } from '@grafana/ui'; +import { GrafanaRuleIdentifier } from 'app/types/unified-alerting'; + +import { Spacer } from '../../components/Spacer'; +import { WithReturnButton } from '../../components/WithReturnButton'; +import { Title } from '../../components/rule-viewer/RuleViewer'; +import { Details } from '../../components/rule-viewer/tabs/Details'; +import { QueryResults } from '../../components/rule-viewer/tabs/Query'; +import { useCombinedRule } from '../../hooks/useCombinedRule'; +import { stringifyErrorLike } from '../../utils/misc'; +import { rulesNav } from '../../utils/navigation'; +import { getRulePluginOrigin, isPausedRule, prometheusRuleType, rulerRuleType } from '../../utils/rules'; + +interface RuleDetailsDrawerProps { + ruleUID: string; + onClose: () => void; +} + +enum DrawerTab { + Query = 'query', + Details = 'details', +} + +export function RuleDetailsDrawer({ ruleUID, onClose }: RuleDetailsDrawerProps) { + const [activeTab, setActiveTab] = useState(DrawerTab.Query); + + // Create rule identifier for Grafana managed rules + const ruleIdentifier: GrafanaRuleIdentifier = useMemo( + () => ({ + uid: ruleUID, + ruleSourceName: 'grafana', + }), + [ruleUID] + ); + + // Fetch rule data + const { + loading, + error, + result: rule, + } = useCombinedRule({ + ruleIdentifier, + }); + + if (error) { + return ( + + + + ); + } + + if (loading || !rule) { + return ( + +
{t('alerting.common.loading', 'Loading...')}
+
+ ); + } + + const { rulerRule, promRule } = rule; + const isPaused = rulerRuleType.grafana.rule(rulerRule) && isPausedRule(rulerRule); + const ruleOrigin = rulerRule ? getRulePluginOrigin(rulerRule) : getRulePluginOrigin(promRule); + + return ( + + + + <Spacer /> + <Box marginRight={4}> + <WithReturnButton + component={ + <LinkButton + icon="eye" + variant="secondary" + href={rulesNav.detailsPageLink('grafana', { + ruleSourceName: 'grafana', + uid: rule.uid ?? '', + })} + target="_blank" + size="sm" + > + <Trans i18nKey="alerting.rule-details-drawer.go-to-detail-view">View alert rule</Trans> + </LinkButton> + } + /> + </Box> + </Stack> + <Text color="secondary">{t('alerting.triage.rule-details.subtitle', 'Rule details and conditions')}</Text> + </Stack> + } + size="lg" + tabs={ + <TabsBar> + <Tab + label={t('alerting.rule-viewer.tab.query-conditions', 'Query and conditions')} + active={activeTab === DrawerTab.Query} + onChangeTab={() => setActiveTab(DrawerTab.Query)} + /> + <Tab + label={t('alerting.rule-viewer.tab.details', 'Details')} + active={activeTab === DrawerTab.Details} + onChangeTab={() => setActiveTab(DrawerTab.Details)} + /> + </TabsBar> + } + > + <TabContent> + {activeTab === DrawerTab.Query && <QueryResults rule={rule} />} + {activeTab === DrawerTab.Details && <Details rule={rule} />} + </TabContent> + </Drawer> + ); +} + +interface ErrorContentProps { + error: unknown; +} + +function ErrorContent({ error }: ErrorContentProps) { + if (isFetchError(error) && error.status === 404) { + return ( + <Alert title={t('alerting.triage.rule-not-found.title', 'Rule not found')} severity="error"> + {t('alerting.triage.rule-not-found.description', 'The requested rule could not be found.')} + </Alert> + ); + } + + return ( + <Alert title={t('alerting.triage.error-loading-rule', 'Error loading rule')} severity="error"> + {stringifyErrorLike(error)} + </Alert> + ); +} diff --git a/public/app/features/alerting/unified/triage/scene/AlertRuleInstances.tsx b/public/app/features/alerting/unified/triage/scene/AlertRuleInstances.tsx index 9d2889d9cae..6ac104d2a76 100644 --- a/public/app/features/alerting/unified/triage/scene/AlertRuleInstances.tsx +++ b/public/app/features/alerting/unified/triage/scene/AlertRuleInstances.tsx @@ -90,6 +90,7 @@ export function AlertRuleInstances({ ruleUID, depth = 0 }: AlertRuleInstancesPro commonLabels={commonLabels} leftColumnWidth={leftColumnWidth} timeRange={timeRange} + ruleUID={ruleUID} depth={depth} /> ))} diff --git a/public/app/features/alerting/unified/triage/scene/SummaryStats.tsx b/public/app/features/alerting/unified/triage/scene/SummaryStats.tsx index add4f86934e..73957f85789 100644 --- a/public/app/features/alerting/unified/triage/scene/SummaryStats.tsx +++ b/public/app/features/alerting/unified/triage/scene/SummaryStats.tsx @@ -32,12 +32,12 @@ export function SummaryStatsReact() { const firstFrame = data?.series?.at(0); if (isLoading || !firstFrame) { - return null; + return <div />; } const dfv = new DataFrameView<Frame>(firstFrame); if (dfv.length === 0) { - return null; + return <div />; } const firingIndex = dfv.fields.alertstate.values.findIndex((state) => state === 'firing'); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index f3afe2eb828..df3dc82617e 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1655,6 +1655,15 @@ "inspector-yaml-tab": { "apply": "Apply" }, + "instance-details": { + "history-error": "Failed to load state history", + "history-error-desc": "Unable to fetch state transition history for this instance.", + "no-history": "No recent state changes", + "state-history": "Recent State Changes" + }, + "instance-details-drawer": { + "title-instance-details": "Instance Details" + }, "instance-match": { "non-matching-labels": "Non-matching labels", "notification-policy": "View route" @@ -2314,6 +2323,9 @@ "rule-details-data-sources": { "label-data-source": "Data source" }, + "rule-details-drawer": { + "go-to-detail-view": "View alert rule" + }, "rule-details-expression": { "label-expression": "Expression" }, @@ -2560,6 +2572,10 @@ "alert-message": "Alert rule has been added or updated. Changes may take up to a minute to appear on the Alert rules list view.", "alert-title": "Update in progress" }, + "tab": { + "details": "Details", + "query-conditions": "Query and conditions" + }, "title-something-wrong-evaluating-alert": "Something went wrong when evaluating this alert rule" }, "rules": { @@ -2930,10 +2946,22 @@ }, "triage": { "alert-instances": "Alert instances", + "error-loading-rule": "Error loading rule", "firing-instances-count": "{{firingCount}} firing instances", + "instance-details": "Instance Details", "no-instances-found": "No alert instances found for rule: {ruleUID}", "no-labels": "No labels", - "pending-instances-count": "{{pendingCount}} pending instances" + "open-in-sidebar": "Open in sidebar", + "open-rule-details": "Open rule details", + "pending-instances-count": "{{pendingCount}} pending instances", + "rule-details": { + "subtitle": "Rule details and conditions", + "title": "Rule Details" + }, + "rule-not-found": { + "description": "The requested rule could not be found.", + "title": "Rule not found" + } }, "type-selector-button": { "add-expression": "Add expression" From 394922c4c85d92384681d015a2074bfda87fae40 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Tue, 14 Oct 2025 07:16:34 -0600 Subject: [PATCH 04/75] Dashboard Migrations: V6 pulldowns to annotations; variables (#112221) * migrate to v19 * migrate to v18 * Migration to be verified: v17 Convert minSpan to maxPerRow in panels * Migration to be verified: 16 Grid layout migration * Refactor v17 and v19 migrations to use shared helper functions * Migration to be verified: 15 No-op migration for schema consistency * Migration to be verified: 14 Shared crosshair to graph tooltip migration * cleanup * wip * complete migration * fix lint issues * refactor and test with minimal graph config * update tests * migrate to v12 * extract defaults outside the func * lint * lint * add missing showValues prop * migrate to v11 * migrate to v10 * add test files * update * migrate to v9 * migrate to v8 * add context and fix latest version * add context * add context * generate snapshots * v13 should be no-op * clean up * fix tests * add context * snapshots * generate snapshots * update * snapshots * wip * fix test * remove nav when cleaning up defaults * migrate to v6 * update comments * remove v28 * remove singlestat migraiton from frontend migrator because this is an automigration * remove unused function * Remove v24 table plugin logic * cleanup * remove plugin version for automigrate as it was used only in v24 and v28 that have been removed * cleanup * update snapshot * update snapshot * update snapshot * update snapshot * remove test --------- Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> --- .../migration/conversion/conversion_test.go | 17 - .../pkg/migration/frontend_defaults.go | 4 +- apps/dashboard/pkg/migration/migrate.go | 6 +- .../pkg/migration/schemaversion/migrations.go | 3 +- .../pkg/migration/schemaversion/v6.go | 100 ++++ .../pkg/migration/schemaversion/v6_test.go | 541 ++++++++++++++++++ .../input/v6.pulldowns_and_templating.json | 106 ++++ .../v6.pulldowns_and_templating.v42.json | 154 +++++ .../v6.pulldowns_and_templating.v6.json | 123 ++++ 9 files changed, 1033 insertions(+), 21 deletions(-) create mode 100644 apps/dashboard/pkg/migration/schemaversion/v6.go create mode 100644 apps/dashboard/pkg/migration/schemaversion/v6_test.go create mode 100644 apps/dashboard/pkg/migration/testdata/input/v6.pulldowns_and_templating.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/latest_version/v6.pulldowns_and_templating.v42.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/single_version/v6.pulldowns_and_templating.v6.json diff --git a/apps/dashboard/pkg/migration/conversion/conversion_test.go b/apps/dashboard/pkg/migration/conversion/conversion_test.go index 9ee40f17ad7..2ea168e9522 100644 --- a/apps/dashboard/pkg/migration/conversion/conversion_test.go +++ b/apps/dashboard/pkg/migration/conversion/conversion_test.go @@ -305,23 +305,6 @@ func TestConversionMetrics(t *testing.T) { expectedSourceSchema: "v2alpha1", expectedTargetSchema: "v2beta1", }, - { - name: "v0 to v1 conversion with minimum version error (succeeds but marks failed)", - source: &dashv0.Dashboard{ - ObjectMeta: metav1.ObjectMeta{UID: "test-uid-4"}, - Spec: common.Unstructured{Object: map[string]any{ - "title": "old dashboard", - "schemaVersion": 5, // Below minimum version (13) - }}, - }, - target: &dashv1.Dashboard{}, - expectAPISuccess: true, // Conversion succeeds but status indicates failure - expectMetricsSuccess: false, - expectedSourceAPI: dashv0.APIVERSION, - expectedTargetAPI: dashv1.APIVERSION, - expectedSourceSchema: "5", - expectedTargetSchema: fmt.Sprintf("%d", 41), // LATEST_VERSION - }, } for _, tt := range tests { diff --git a/apps/dashboard/pkg/migration/frontend_defaults.go b/apps/dashboard/pkg/migration/frontend_defaults.go index 517c1eccc25..08a9afe8475 100644 --- a/apps/dashboard/pkg/migration/frontend_defaults.go +++ b/apps/dashboard/pkg/migration/frontend_defaults.go @@ -66,7 +66,6 @@ func applyFrontendDefaults(dashboard map[string]interface{}) { sortPanelsByGridPos(dashboard) // Built-in components - addBuiltInAnnotationQuery(dashboard) initMeta(dashboard) // Variable cleanup @@ -1053,7 +1052,8 @@ func cleanupDashboardDefaults(dashboard map[string]interface{}) { // These properties are lost during frontend's property copying loop in getSaveModelCloneOld() delete(dashboard, "preload") // Transient dashboard loading state delete(dashboard, "iteration") // Template variable iteration timestamp - delete(dashboard, "nav") + delete(dashboard, "nav") // Removed after V7 migration + delete(dashboard, "pulldowns") // Removed after V6 migration - frontend doesn't have this property } // cleanupFieldConfigDefaults removes properties that frontend considers as defaults and omits diff --git a/apps/dashboard/pkg/migration/migrate.go b/apps/dashboard/pkg/migration/migrate.go index 73fdf460416..08cba231fd3 100644 --- a/apps/dashboard/pkg/migration/migrate.go +++ b/apps/dashboard/pkg/migration/migrate.go @@ -126,7 +126,11 @@ func (m *migrator) migrate(ctx context.Context, dash map[string]interface{}, tar } } - // 6. Clean up the dashboard to match frontend getSaveModel behavior + // 6. Add built-in annotation query after all migrations are complete + // This matches the frontend DashboardModel constructor behavior + addBuiltInAnnotationQuery(dash) + + // 7. Clean up the dashboard to match frontend getSaveModel behavior // This removes properties that shouldn't be persisted and filters out default values cleanupDashboardForSave(dash) diff --git a/apps/dashboard/pkg/migration/schemaversion/migrations.go b/apps/dashboard/pkg/migration/schemaversion/migrations.go index 97921173fbf..cdebbf0defe 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migrations.go +++ b/apps/dashboard/pkg/migration/schemaversion/migrations.go @@ -7,7 +7,7 @@ import ( ) const ( - MIN_VERSION = 6 + MIN_VERSION = 5 LATEST_VERSION = 42 ) @@ -35,6 +35,7 @@ type PanelPluginInfo struct { func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionMigrationFunc { return map[int]SchemaVersionMigrationFunc{ + 6: V6, 7: V7, 8: V8, 9: V9, diff --git a/apps/dashboard/pkg/migration/schemaversion/v6.go b/apps/dashboard/pkg/migration/schemaversion/v6.go new file mode 100644 index 00000000000..f5e8af73ba8 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v6.go @@ -0,0 +1,100 @@ +package schemaversion + +import "context" + +// V6 migration handles the pulldowns to annotations conversion and template variable updates. +// This migration moves annotations from the legacy pulldowns array to the new annotations structure +// and updates template variables to use the new schema format. +// +// Background: +// In earlier versions, dashboards used a "pulldowns" property array to store various UI elements +// including annotations. This migration extracts annotations from pulldowns and creates the new +// annotations structure. It also updates template variables to ensure proper datasource handling +// and type normalization. +// +// Example before migration: +// { +// "schemaVersion": 5, +// "pulldowns": [ +// { "type": "filtering", "enable": true }, +// { "type": "annotations", "enable": true, "annotations": [{"name": "old"}] } +// ], +// "templating": { +// "list": [ +// { "name": "server", "type": "filter" }, +// { "name": "metric", "datasource": undefined, "allFormat": undefined } +// ] +// } +// } +// +// Example after migration: +// { +// "schemaVersion": 6, +// "annotations": { +// "list": [{"name": "old"}] +// }, +// "templating": { +// "list": [ +// { "name": "server", "type": "query", "datasource": null }, +// { "name": "metric", "type": "query", "datasource": null } +// ] +// } +// } + +func V6(_ context.Context, dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = 6 + + // Move drop-downs to new schema (matches frontend DashboardMigrator logic) + // Find annotations in pulldowns array: equivalent to find(old.pulldowns, { type: 'annotations' }) + if pulldowns, ok := dashboard["pulldowns"].([]interface{}); ok { + for _, pulldownInterface := range pulldowns { + if pulldown, ok := pulldownInterface.(map[string]interface{}); ok { + if pulldownType, exists := pulldown["type"]; exists && pulldownType == "annotations" { + // Found annotations pulldown, extract annotations + if annotations, hasAnnotations := pulldown["annotations"]; hasAnnotations { + dashboard["annotations"] = map[string]interface{}{ + "list": annotations, + } + } else { + // If no annotations property, create empty list + dashboard["annotations"] = map[string]interface{}{ + "list": []interface{}{}, + } + } + break // Found what we're looking for, no need to continue + } + } + } + } + + // Update template variables + if templating, ok := dashboard["templating"].(map[string]interface{}); ok { + if list, ok := templating["list"].([]interface{}); ok { + for _, variableInterface := range list { + if variable, ok := variableInterface.(map[string]interface{}); ok { + // If datasource is undefined/missing, set to null + if _, exists := variable["datasource"]; !exists { + variable["datasource"] = nil + } + + // Convert 'filter' type to 'query' + if varType, exists := variable["type"]; exists && varType == "filter" { + variable["type"] = "query" + } + + // If type is undefined/missing, set to 'query' + if _, exists := variable["type"]; !exists { + variable["type"] = "query" + } + + // Remove allFormat if it's undefined + if allFormat, exists := variable["allFormat"]; exists && allFormat == nil { + delete(variable, "allFormat") + } + } + } + } + } + + return nil +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v6_test.go b/apps/dashboard/pkg/migration/schemaversion/v6_test.go new file mode 100644 index 00000000000..6668c8392c6 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v6_test.go @@ -0,0 +1,541 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV6Migration(t *testing.T) { + testCases := []migrationTestCase{ + { + name: "pulldowns to annotations conversion with existing annotations", + input: map[string]interface{}{ + "schemaVersion": 5, + "pulldowns": []interface{}{ + map[string]interface{}{ + "type": "filtering", + "enable": true, + }, + map[string]interface{}{ + "type": "annotations", + "enable": true, + "annotations": []interface{}{ + map[string]interface{}{ + "name": "old annotation", + "datasource": "prometheus", + "enable": true, + }, + }, + }, + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "filter", + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "pulldowns": []interface{}{ + map[string]interface{}{ + "type": "filtering", + "enable": true, + }, + map[string]interface{}{ + "type": "annotations", + "enable": true, + "annotations": []interface{}{ + map[string]interface{}{ + "name": "old annotation", + "datasource": "prometheus", + "enable": true, + }, + }, + }, + }, + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "old annotation", + "datasource": "prometheus", + "enable": true, + }, + }, + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "query", + "datasource": nil, + }, + }, + }, + }, + }, + { + name: "pulldowns to annotations conversion with empty annotations", + input: map[string]interface{}{ + "schemaVersion": 5, + "pulldowns": []interface{}{ + map[string]interface{}{ + "type": "annotations", + "enable": true, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "pulldowns": []interface{}{ + map[string]interface{}{ + "type": "annotations", + "enable": true, + }, + }, + "annotations": map[string]interface{}{ + "list": []interface{}{}, + }, + }, + }, + { + name: "no annotations pulldown found", + input: map[string]interface{}{ + "schemaVersion": 5, + "pulldowns": []interface{}{ + map[string]interface{}{ + "type": "filtering", + "enable": true, + }, + map[string]interface{}{ + "type": "other", + "enable": false, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "pulldowns": []interface{}{ + map[string]interface{}{ + "type": "filtering", + "enable": true, + }, + map[string]interface{}{ + "type": "other", + "enable": false, + }, + }, + }, + }, + { + name: "no pulldowns property", + input: map[string]interface{}{ + "schemaVersion": 5, + "title": "Test Dashboard", + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "title": "Test Dashboard", + }, + }, + { + name: "empty pulldowns array", + input: map[string]interface{}{ + "schemaVersion": 5, + "pulldowns": []interface{}{}, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "pulldowns": []interface{}{}, + }, + }, + { + name: "template variables migration - filter to query type", + input: map[string]interface{}{ + "schemaVersion": 5, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "filter", + }, + map[string]interface{}{ + "name": "metric", + "type": "query", + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "query", + "datasource": nil, + }, + map[string]interface{}{ + "name": "metric", + "type": "query", + "datasource": nil, + }, + }, + }, + }, + }, + { + name: "template variables migration - missing type becomes query", + input: map[string]interface{}{ + "schemaVersion": 5, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "query", + "datasource": nil, + }, + }, + }, + }, + }, + { + name: "template variables migration - existing datasource preserved", + input: map[string]interface{}{ + "schemaVersion": 5, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "query", + "datasource": "prometheus", + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "query", + "datasource": "prometheus", + }, + }, + }, + }, + }, + { + name: "template variables migration - allFormat removal", + input: map[string]interface{}{ + "schemaVersion": 5, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "query", + "allFormat": nil, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "query", + "datasource": nil, + }, + }, + }, + }, + }, + { + name: "template variables migration - allFormat with value preserved", + input: map[string]interface{}{ + "schemaVersion": 5, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "query", + "allFormat": "glob", + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "query", + "datasource": nil, + "allFormat": "glob", + }, + }, + }, + }, + }, + { + name: "no templating property", + input: map[string]interface{}{ + "schemaVersion": 5, + "title": "Test Dashboard", + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "title": "Test Dashboard", + }, + }, + { + name: "empty templating list", + input: map[string]interface{}{ + "schemaVersion": 5, + "templating": map[string]interface{}{ + "list": []interface{}{}, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "templating": map[string]interface{}{ + "list": []interface{}{}, + }, + }, + }, + { + name: "complex dashboard with both pulldowns and templating", + input: map[string]interface{}{ + "schemaVersion": 5, + "title": "Complex Dashboard", + "pulldowns": []interface{}{ + map[string]interface{}{ + "type": "filtering", + "enable": true, + }, + map[string]interface{}{ + "type": "annotations", + "enable": true, + "annotations": []interface{}{ + map[string]interface{}{ + "name": "deployment", + "datasource": "prometheus", + "enable": true, + "iconColor": "red", + }, + map[string]interface{}{ + "name": "alerts", + "datasource": "loki", + "enable": false, + }, + }, + }, + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "environment", + "type": "filter", + "allFormat": nil, + }, + map[string]interface{}{ + "name": "service", + "datasource": "prometheus", + "allFormat": "glob", + }, + map[string]interface{}{ + "name": "region", + "type": "custom", + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "title": "Complex Dashboard", + "pulldowns": []interface{}{ + map[string]interface{}{ + "type": "filtering", + "enable": true, + }, + map[string]interface{}{ + "type": "annotations", + "enable": true, + "annotations": []interface{}{ + map[string]interface{}{ + "name": "deployment", + "datasource": "prometheus", + "enable": true, + "iconColor": "red", + }, + map[string]interface{}{ + "name": "alerts", + "datasource": "loki", + "enable": false, + }, + }, + }, + }, + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "deployment", + "datasource": "prometheus", + "enable": true, + "iconColor": "red", + }, + map[string]interface{}{ + "name": "alerts", + "datasource": "loki", + "enable": false, + }, + }, + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "environment", + "type": "query", + "datasource": nil, + }, + map[string]interface{}{ + "name": "service", + "type": "query", + "datasource": "prometheus", + "allFormat": "glob", + }, + map[string]interface{}{ + "name": "region", + "type": "custom", + "datasource": nil, + }, + }, + }, + }, + }, + { + name: "invalid pulldowns structure", + input: map[string]interface{}{ + "schemaVersion": 5, + "pulldowns": "invalid_structure", + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "pulldowns": "invalid_structure", + }, + }, + { + name: "invalid templating structure", + input: map[string]interface{}{ + "schemaVersion": 5, + "templating": "invalid_structure", + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "templating": "invalid_structure", + }, + }, + { + name: "invalid templating list structure", + input: map[string]interface{}{ + "schemaVersion": 5, + "templating": map[string]interface{}{ + "list": "invalid_list", + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "templating": map[string]interface{}{ + "list": "invalid_list", + }, + }, + }, + { + name: "pulldown item with invalid structure", + input: map[string]interface{}{ + "schemaVersion": 5, + "pulldowns": []interface{}{ + "invalid_pulldown", + map[string]interface{}{ + "type": "annotations", + "enable": true, + "annotations": []interface{}{ + map[string]interface{}{ + "name": "valid annotation", + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "pulldowns": []interface{}{ + "invalid_pulldown", + map[string]interface{}{ + "type": "annotations", + "enable": true, + "annotations": []interface{}{ + map[string]interface{}{ + "name": "valid annotation", + }, + }, + }, + }, + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "valid annotation", + }, + }, + }, + }, + }, + { + name: "template variable with invalid structure", + input: map[string]interface{}{ + "schemaVersion": 5, + "templating": map[string]interface{}{ + "list": []interface{}{ + "invalid_variable", + map[string]interface{}{ + "name": "valid_variable", + "type": "filter", + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": 6, + "templating": map[string]interface{}{ + "list": []interface{}{ + "invalid_variable", + map[string]interface{}{ + "name": "valid_variable", + "type": "query", + "datasource": nil, + }, + }, + }, + }, + }, + } + + runMigrationTests(t, testCases, schemaversion.V6) +} diff --git a/apps/dashboard/pkg/migration/testdata/input/v6.pulldowns_and_templating.json b/apps/dashboard/pkg/migration/testdata/input/v6.pulldowns_and_templating.json new file mode 100644 index 00000000000..73c1db88803 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v6.pulldowns_and_templating.json @@ -0,0 +1,106 @@ +{ + "title": "V6 Pulldowns and Template Variables Migration Test", + "schemaVersion": 5, + "pulldowns": [ + { + "type": "filtering", + "enable": true + }, + { + "type": "annotations", + "enable": true, + "annotations": [ + { + "name": "deployment", + "datasource": "prometheus", + "enable": true, + "iconColor": "red", + "query": "ALERTS{alertname=\"DeploymentStarted\"}" + }, + { + "name": "alerts", + "datasource": "loki", + "enable": false, + "iconColor": "yellow", + "query": "{job=\"alertmanager\"}" + } + ] + } + ], + "templating": { + "list": [ + { + "name": "environment", + "type": "filter", + "allFormat": null + }, + { + "name": "service", + "datasource": "prometheus", + "allFormat": "glob" + }, + { + "name": "region", + "type": "custom", + "options": [ + { + "text": "us-east-1", + "value": "us-east-1" + }, + { + "text": "us-west-2", + "value": "us-west-2" + } + ] + }, + { + "name": "instance", + "type": "query", + "datasource": "prometheus", + "query": "label_values(up, instance)", + "refresh": 1 + } + ] + }, + "panels": [ + { + "id": 1, + "title": "CPU Usage", + "type": "graph", + "targets": [ + { + "expr": "cpu_usage{environment=\"$environment\", service=\"$service\"}", + "refId": "A" + } + ], + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + } + }, + { + "id": 2, + "title": "Memory Usage", + "type": "singlestat", + "targets": [ + { + "expr": "memory_usage{region=\"$region\"}", + "refId": "B" + } + ], + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + } + } + ], + "time": { + "from": "now-1h", + "to": "now" + }, + "refresh": "30s" +} diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v6.pulldowns_and_templating.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v6.pulldowns_and_templating.v42.json new file mode 100644 index 00000000000..1a18f9b01c2 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v6.pulldowns_and_templating.v42.json @@ -0,0 +1,154 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": { + "uid": "prometheus" + }, + "enable": true, + "iconColor": "red", + "name": "deployment", + "query": "ALERTS{alertname=\"DeploymentStarted\"}" + }, + { + "datasource": { + "uid": "loki" + }, + "enable": false, + "iconColor": "yellow", + "name": "alerts", + "query": "{job=\"alertmanager\"}" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "cpu_usage{environment=\"$environment\", service=\"$service\"}", + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "expr": "memory_usage{region=\"$region\"}", + "refId": "B" + } + ], + "title": "Memory Usage", + "type": "stat" + } + ], + "refresh": "30s", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "name": "environment", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "allFormat": "glob", + "datasource": "prometheus", + "name": "service", + "options": [], + "refresh": 1, + "type": "query" + }, + { + "name": "region", + "options": [ + { + "text": "us-east-1", + "value": "us-east-1" + }, + { + "text": "us-west-2", + "value": "us-west-2" + } + ], + "type": "custom" + }, + { + "datasource": "prometheus", + "name": "instance", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V6 Pulldowns and Template Variables Migration Test", + "weekStart": "" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v6.pulldowns_and_templating.v6.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v6.pulldowns_and_templating.v6.json new file mode 100644 index 00000000000..8cdf9a36a40 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v6.pulldowns_and_templating.v6.json @@ -0,0 +1,123 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + }, + { + "datasource": "prometheus", + "enable": true, + "iconColor": "red", + "name": "deployment", + "query": "ALERTS{alertname=\"DeploymentStarted\"}" + }, + { + "datasource": "loki", + "enable": false, + "iconColor": "yellow", + "name": "alerts", + "query": "{job=\"alertmanager\"}" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graph", + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "targets": [ + { + "expr": "cpu_usage{environment=\"$environment\", service=\"$service\"}", + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "autoMigrateFrom": "singlestat", + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "targets": [ + { + "expr": "memory_usage{region=\"$region\"}", + "refId": "B" + } + ], + "title": "Memory Usage", + "type": "stat" + } + ], + "refresh": "30s", + "schemaVersion": 6, + "tags": [], + "templating": { + "list": [ + { + "name": "environment", + "options": [], + "type": "query" + }, + { + "allFormat": "glob", + "datasource": "prometheus", + "name": "service", + "options": [], + "type": "query" + }, + { + "name": "region", + "options": [ + { + "text": "us-east-1", + "value": "us-east-1" + }, + { + "text": "us-west-2", + "value": "us-west-2" + } + ], + "type": "custom" + }, + { + "datasource": "prometheus", + "name": "instance", + "options": [], + "query": "label_values(up, instance)", + "refresh": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V6 Pulldowns and Template Variables Migration Test", + "weekStart": "" +} \ No newline at end of file From e14a438116ed979cf76c19e31e8c2f4633603861 Mon Sep 17 00:00:00 2001 From: Alexa Vargas <239999+axelavargas@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:33:35 +0200 Subject: [PATCH 05/75] E2E: Fix dashboard-new-layout flaky test goToEmbeddedPanel (#112380) E2E: Fix dashboard-new-layout flaky test --- e2e-playwright/dashboard-new-layouts/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-playwright/dashboard-new-layouts/utils.ts b/e2e-playwright/dashboard-new-layouts/utils.ts index 70f5aa41bf0..c59f782b37a 100644 --- a/e2e-playwright/dashboard-new-layouts/utils.ts +++ b/e2e-playwright/dashboard-new-layouts/utils.ts @@ -185,7 +185,7 @@ export async function goToEmbeddedPanel(page: Page) { const baseUrl = currentUrl.match(baseUrlRegex)?.[0]; soloPanelUrl = soloPanelUrl!.replace(baseUrlRegex, baseUrl!); - page.goto(soloPanelUrl!); + await page.goto(soloPanelUrl!); } export async function moveTab( From f27900702cc165f011689516406c8c9453dcf445 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Tue, 14 Oct 2025 07:53:13 -0600 Subject: [PATCH 06/75] Dashboard Migrations: V5 - no-op (#112224) * migrate to v19 * migrate to v18 * Migration to be verified: v17 Convert minSpan to maxPerRow in panels * Migration to be verified: 16 Grid layout migration * Refactor v17 and v19 migrations to use shared helper functions * Migration to be verified: 15 No-op migration for schema consistency * Migration to be verified: 14 Shared crosshair to graph tooltip migration * cleanup * wip * complete migration * fix lint issues * refactor and test with minimal graph config * update tests * migrate to v12 * extract defaults outside the func * lint * lint * add missing showValues prop * migrate to v11 * migrate to v10 * add test files * update * migrate to v9 * migrate to v8 * add context and fix latest version * add context * add context * generate snapshots * v13 should be no-op * clean up * fix tests * add context * snapshots * generate snapshots * update * snapshots * wip * fix test * remove nav when cleaning up defaults * migrate to v6 * update comments * migrate to v5 * remove v28 * remove singlestat migraiton from frontend migrator because this is an automigration * remove unused function * Remove v24 table plugin logic * cleanup * remove plugin version for automigrate as it was used only in v24 and v28 that have been removed * cleanup * update snapshot * update snapshot * update snapshot * update snapshot * update snapshot --------- Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> --- .../pkg/migration/schemaversion/migrations.go | 3 +- .../pkg/migration/schemaversion/v5.go | 11 +++ .../pkg/migration/schemaversion/v5_test.go | 38 +++++++ .../migration/testdata/input/v5.no-op.json | 18 ++++ .../output/latest_version/v5.no-op.v42.json | 98 +++++++++++++++++++ .../output/single_version/v5.no-op.v5.json | 52 ++++++++++ 6 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 apps/dashboard/pkg/migration/schemaversion/v5.go create mode 100644 apps/dashboard/pkg/migration/schemaversion/v5_test.go create mode 100644 apps/dashboard/pkg/migration/testdata/input/v5.no-op.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/latest_version/v5.no-op.v42.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/single_version/v5.no-op.v5.json diff --git a/apps/dashboard/pkg/migration/schemaversion/migrations.go b/apps/dashboard/pkg/migration/schemaversion/migrations.go index cdebbf0defe..9242ff8356f 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migrations.go +++ b/apps/dashboard/pkg/migration/schemaversion/migrations.go @@ -7,7 +7,7 @@ import ( ) const ( - MIN_VERSION = 5 + MIN_VERSION = 4 LATEST_VERSION = 42 ) @@ -35,6 +35,7 @@ type PanelPluginInfo struct { func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionMigrationFunc { return map[int]SchemaVersionMigrationFunc{ + 5: V5, 6: V6, 7: V7, 8: V8, diff --git a/apps/dashboard/pkg/migration/schemaversion/v5.go b/apps/dashboard/pkg/migration/schemaversion/v5.go new file mode 100644 index 00000000000..976aa5333ab --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v5.go @@ -0,0 +1,11 @@ +package schemaversion + +import ( + "context" +) + +// V5 is a no-op migration +func V5(_ context.Context, dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = 5 + return nil +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v5_test.go b/apps/dashboard/pkg/migration/schemaversion/v5_test.go new file mode 100644 index 00000000000..fe0f137667a --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v5_test.go @@ -0,0 +1,38 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV5(t *testing.T) { + tests := []migrationTestCase{ + { + name: "v5 no-op migration, updates schema version only", + input: map[string]interface{}{ + "title": "V5 No-Op Migration Test Dashboard", + "schemaVersion": 4, + "panels": []interface{}{ + map[string]interface{}{ + "type": "graph", + "title": "Panel remains unchanged", + "id": 1, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V5 No-Op Migration Test Dashboard", + "schemaVersion": 5, + "panels": []interface{}{ + map[string]interface{}{ + "type": "graph", + "title": "Panel remains unchanged", + "id": 1, + }, + }, + }, + }, + } + runMigrationTests(t, tests, schemaversion.V5) +} diff --git a/apps/dashboard/pkg/migration/testdata/input/v5.no-op.json b/apps/dashboard/pkg/migration/testdata/input/v5.no-op.json new file mode 100644 index 00000000000..64132050b11 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v5.no-op.json @@ -0,0 +1,18 @@ +{ + "title": "V5 No-Op Migration Test", + "schemaVersion": 4, + "panels": [ + { + "id": 1, + "type": "singlestat" + }, + { + "id": 2, + "type": "singlestat" + }, + { + "id": 3, + "type": "graph" + } + ] +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v5.no-op.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v5.no-op.v42.json new file mode 100644 index 00000000000..09f15a0f5c1 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v5.no-op.v42.json @@ -0,0 +1,98 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V5 No-Op Migration Test", + "weekStart": "" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v5.no-op.v5.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v5.no-op.v5.json new file mode 100644 index 00000000000..7d9a3d9866f --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v5.no-op.v5.json @@ -0,0 +1,52 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "singlestat", + "id": 1, + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "id": 2, + "type": "stat" + }, + { + "autoMigrateFrom": "graph", + "id": 3, + "type": "timeseries" + } + ], + "schemaVersion": 5, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V5 No-Op Migration Test", + "weekStart": "" +} \ No newline at end of file From aeb041a9af3470335571eac1152e9667570a9dde Mon Sep 17 00:00:00 2001 From: Ieva <ieva.vasiljeva@grafana.com> Date: Tue, 14 Oct 2025 14:54:19 +0100 Subject: [PATCH 07/75] Folder permissions: Remove default creator can Admin permissions for folders (#111984) * Remove default creator can Admin permissions for folders * Actually, we only need to set permissions for root level folders * Put back creator permission * linting * use FT for assessing what permissions to set * fix tests --- pkg/registry/apis/folders/folder_storage.go | 13 +++++++++-- .../apis/folders/folder_storage_test.go | 2 ++ pkg/registry/apis/folders/register.go | 1 + .../dashboards/service/dashboard_service.go | 22 +++++++------------ 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/pkg/registry/apis/folders/folder_storage.go b/pkg/registry/apis/folders/folder_storage.go index 8d96a494859..4b55a55390d 100644 --- a/pkg/registry/apis/folders/folder_storage.go +++ b/pkg/registry/apis/folders/folder_storage.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" ) @@ -39,6 +40,7 @@ type folderStorage struct { tableConverter rest.TableConvertor permissionsOnCreate bool // cfg.RBAC.PermissionsOnCreation("folder") + features featuremgmt.FeatureToggles folderPermissionsSvc accesscontrol.FolderPermissionsService acService accesscontrol.Service } @@ -152,23 +154,30 @@ func (s *folderStorage) DeleteCollection(ctx context.Context, deleteValidation r func (s *folderStorage) setDefaultFolderPermissions(ctx context.Context, orgID int64, user identity.Requester, uid, parentUID string) error { var permissions []accesscontrol.SetResourcePermissionCommand + isNested := parentUID != "" + if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesDashboards) && isNested { + // No permissions on nested folders when kubernetesDashboards is enabled + return nil + } + + // Creator permissions always set with the legacy behaviour and set on root level folders for new behaviour if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { userID, err := user.GetInternalID() if err != nil { return err } - permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(), }) } - isNested := parentUID != "" + if !isNested { permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, {BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, }...) } + _, err := s.folderPermissionsSvc.SetPermissions(ctx, orgID, uid, permissions...) if err != nil { return err diff --git a/pkg/registry/apis/folders/folder_storage_test.go b/pkg/registry/apis/folders/folder_storage_test.go index a37bd38ed6a..d03b0947f5d 100644 --- a/pkg/registry/apis/folders/folder_storage_test.go +++ b/pkg/registry/apis/folders/folder_storage_test.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) @@ -54,6 +55,7 @@ func TestSetDefaultPermissionsWhenCreatingFolder(t *testing.T) { acService: actest.FakeService{}, store: store, permissionsOnCreate: cfg.RBAC.PermissionsOnCreation("folder"), + features: featuremgmt.WithFeatures(), } obj := &folders.Folder{} diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index 4f818f40ea8..6bfdced4089 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -168,6 +168,7 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API b.storage = &folderStorage{ tableConverter: resourceInfo.TableConverter(), folderPermissionsSvc: b.folderPermissionsSvc, + features: b.features, acService: b.acService, permissionsOnCreate: b.permissionsOnCreate, store: dw, diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index b9962afd285..170c924b6cd 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -1179,30 +1179,24 @@ func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Con return err } permissions := []accesscontrol.SetResourcePermissionCommand{} + + isNested := obj.GetFolder() != "" + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesDashboards) && isNested { + // Don't set any permissions for nested dashboards + return nil + } if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ UserID: uid, Permission: dashboardaccess.PERMISSION_ADMIN.String(), }) } - isNested := obj.GetFolder() != "" - if !dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesDashboards) { - // legacy behavior - if !isNested { - permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ - {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, - {BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, - }...) - } - } else { - // Don't set any permissions for nested dashboards - if isNested { - return nil - } + if !isNested { permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, {BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, }...) } + svc := dr.getPermissionsService(key.Resource == "folders") if _, err := svc.SetPermissions(ctx, ns.OrgID, obj.GetName(), permissions...); err != nil { logger.Error("Could not set default permissions", "error", err) From 7395f803c1790b5189eca55b50187f3c66c76501 Mon Sep 17 00:00:00 2001 From: Mihai Doarna <mihai.doarna@grafana.com> Date: Tue, 14 Oct 2025 17:20:59 +0300 Subject: [PATCH 08/75] IAM: Implement create method for team bindings (#112003) * implement create method for team bindings * generate openapi specs * enable dual writer for team bindings * add validation rules * generate openapi * add integration tests and fix remaining issues * fix validation test * use UIDs instead of IDs to build team binding name * use IDs when generating the TeamBinding name * fix build error * remove time truncate --- pkg/registry/apis/iam/authorizer.go | 1 + .../iam/legacy/create_team_member_query.sql | 5 + pkg/registry/apis/iam/legacy/sql.go | 2 + pkg/registry/apis/iam/legacy/sql_test.go | 36 +- pkg/registry/apis/iam/legacy/team.go | 103 ++- .../apis/iam/legacy/team_bindings_query.sql | 9 +- ...e_team_member_query-create_team_member.sql | 5 + ..._member_query-create_team_member_admin.sql | 5 + ...l--team_bindings_query-team_1_bindings.sql | 5 +- ...--team_bindings_query-team_bindings_id.sql | 11 + ...am_bindings_query-team_bindings_page_1.sql | 2 +- ...am_bindings_query-team_bindings_page_2.sql | 2 +- ...e_team_member_query-create_team_member.sql | 5 + ..._member_query-create_team_member_admin.sql | 5 + ...s--team_bindings_query-team_1_bindings.sql | 5 +- ...--team_bindings_query-team_bindings_id.sql | 11 + ...am_bindings_query-team_bindings_page_1.sql | 2 +- ...am_bindings_query-team_bindings_page_2.sql | 2 +- ...e_team_member_query-create_team_member.sql | 5 + ..._member_query-create_team_member_admin.sql | 5 + ...e--team_bindings_query-team_1_bindings.sql | 5 +- ...--team_bindings_query-team_bindings_id.sql | 11 + ...am_bindings_query-team_bindings_page_1.sql | 2 +- ...am_bindings_query-team_bindings_page_2.sql | 2 +- pkg/registry/apis/iam/register.go | 19 +- pkg/registry/apis/iam/team/store_binding.go | 123 ++- pkg/registry/apis/iam/team/validate.go | 21 + pkg/registry/apis/iam/team/validate_test.go | 110 +++ pkg/tests/apis/iam/iam_test.go | 6 + .../iam/team_bindings_integration_test.go | 295 +++++++ .../iam/testdata/team-test-create-v0.yaml | 2 +- .../iam/testdata/team-test-update-v0.yaml | 2 +- .../testdata/teambinding-test-create-v0.yaml | 10 + .../iam.grafana.app-v0alpha1.json | 784 ++++++++++++++++-- 34 files changed, 1494 insertions(+), 124 deletions(-) create mode 100644 pkg/registry/apis/iam/legacy/create_team_member_query.sql create mode 100755 pkg/registry/apis/iam/legacy/testdata/mysql--create_team_member_query-create_team_member.sql create mode 100755 pkg/registry/apis/iam/legacy/testdata/mysql--create_team_member_query-create_team_member_admin.sql create mode 100755 pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_id.sql create mode 100755 pkg/registry/apis/iam/legacy/testdata/postgres--create_team_member_query-create_team_member.sql create mode 100755 pkg/registry/apis/iam/legacy/testdata/postgres--create_team_member_query-create_team_member_admin.sql create mode 100755 pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_id.sql create mode 100755 pkg/registry/apis/iam/legacy/testdata/sqlite--create_team_member_query-create_team_member.sql create mode 100755 pkg/registry/apis/iam/legacy/testdata/sqlite--create_team_member_query-create_team_member_admin.sql create mode 100755 pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_id.sql create mode 100644 pkg/tests/apis/iam/team_bindings_integration_test.go create mode 100644 pkg/tests/apis/iam/testdata/teambinding-test-create-v0.yaml diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index eadb07d7d50..4fe350954ad 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -24,6 +24,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth // Identity specific resources legacyAuthorizer := gfauthorizer.NewResourceAuthorizer(legacyAccessClient) resourceAuthorizer[iamv0.TeamResourceInfo.GetName()] = legacyAuthorizer + resourceAuthorizer[iamv0.TeamBindingResourceInfo.GetName()] = legacyAuthorizer resourceAuthorizer["display"] = legacyAuthorizer // Access specific resources diff --git a/pkg/registry/apis/iam/legacy/create_team_member_query.sql b/pkg/registry/apis/iam/legacy/create_team_member_query.sql new file mode 100644 index 00000000000..0ba42e384e2 --- /dev/null +++ b/pkg/registry/apis/iam/legacy/create_team_member_query.sql @@ -0,0 +1,5 @@ +INSERT INTO {{ .Ident .TeamMemberTable }} + (team_id, user_id, org_id, created, updated, external, permission) +VALUES + ({{ .Arg .Command.TeamID }}, {{ .Arg .Command.UserID }}, {{ .Arg .Command.OrgID }}, {{ .Arg .Command.Created }}, + {{ .Arg .Command.Updated }}, {{ .Arg .Command.External }}, {{ .Arg .Command.Permission }}) diff --git a/pkg/registry/apis/iam/legacy/sql.go b/pkg/registry/apis/iam/legacy/sql.go index f5ef5722d23..f502dee7d86 100644 --- a/pkg/registry/apis/iam/legacy/sql.go +++ b/pkg/registry/apis/iam/legacy/sql.go @@ -35,6 +35,8 @@ type LegacyIdentityStore interface { UpdateTeam(ctx context.Context, ns claims.NamespaceInfo, cmd UpdateTeamCommand) (*UpdateTeamResult, error) ListTeams(ctx context.Context, ns claims.NamespaceInfo, query ListTeamQuery) (*ListTeamResult, error) DeleteTeam(ctx context.Context, ns claims.NamespaceInfo, cmd DeleteTeamCommand) error + + CreateTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd CreateTeamMemberCommand) (*CreateTeamMemberResult, error) ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error) ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error) } diff --git a/pkg/registry/apis/iam/legacy/sql_test.go b/pkg/registry/apis/iam/legacy/sql_test.go index 58c268b6d95..62b4c89edd7 100644 --- a/pkg/registry/apis/iam/legacy/sql_test.go +++ b/pkg/registry/apis/iam/legacy/sql_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/grafana/grafana/pkg/registry/apis/iam/common" + "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/storage/legacysql" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate/mocks" @@ -72,6 +73,12 @@ func TestIdentityQueries(t *testing.T) { return &v } + createTeamMember := func(cmd *CreateTeamMemberCommand) sqltemplate.SQLTemplate { + v := newCreateTeamMember(nodb, cmd) + v.SQLTemplate = mocks.NewTestingSQLTemplate() + return &v + } + listTeamBindings := func(q *ListTeamBindingsQuery) sqltemplate.SQLTemplate { v := newListTeamBindings(nodb, q) v.SQLTemplate = mocks.NewTestingSQLTemplate() @@ -202,12 +209,37 @@ func TestIdentityQueries(t *testing.T) { }), }, }, + sqlCreateTeamMemberQuery: { + { + Name: "create_team_member", + Data: createTeamMember(&CreateTeamMemberCommand{ + TeamID: 1, + UserID: 1, + Created: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)), + Updated: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)), + External: false, + Permission: team.PermissionTypeMember, + }), + }, + { + Name: "create_team_member_admin", + Data: createTeamMember(&CreateTeamMemberCommand{ + TeamID: 1, + UserID: 1, + Created: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)), + Updated: NewDBTime(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)), + External: false, + Permission: team.PermissionTypeAdmin, + }), + }, + }, sqlQueryTeamBindingsTemplate: { { - Name: "team_1_bindings", + Name: "team_bindings_id", Data: listTeamBindings(&ListTeamBindingsQuery{ OrgID: 1, - UID: "team-1", + TeamID: 1, + UserID: 1, Pagination: common.Pagination{Limit: 1}, }), }, diff --git a/pkg/registry/apis/iam/legacy/team.go b/pkg/registry/apis/iam/legacy/team.go index 6f49707d5d0..a94bf44ad0a 100644 --- a/pkg/registry/apis/iam/legacy/team.go +++ b/pkg/registry/apis/iam/legacy/team.go @@ -214,7 +214,7 @@ func (r createTeamQuery) Validate() error { } func (s *legacySQLStore) CreateTeam(ctx context.Context, ns claims.NamespaceInfo, cmd CreateTeamCommand) (*CreateTeamResult, error) { - now := time.Now().UTC().Truncate(time.Second) + now := time.Now().UTC() cmd.Created = NewDBTime(now) cmd.Updated = NewDBTime(now) @@ -300,7 +300,7 @@ func (r updateTeamQuery) Validate() error { } func (s *legacySQLStore) UpdateTeam(ctx context.Context, ns claims.NamespaceInfo, cmd UpdateTeamCommand) (*UpdateTeamResult, error) { - now := time.Now().UTC().Truncate(time.Second) + now := time.Now().UTC() cmd.Updated = NewDBTime(now) @@ -414,8 +414,8 @@ func (s *legacySQLStore) DeleteTeam(ctx context.Context, ns claims.NamespaceInfo } type ListTeamBindingsQuery struct { - // UID is team uid to list bindings for. If not set store should list bindings for all teams - UID string + TeamID int64 + UserID int64 OrgID int64 Pagination common.Pagination } @@ -432,6 +432,7 @@ type TeamMember struct { TeamUID string UserID int64 UserUID string + OrgID int64 Name string Email string Username string @@ -486,7 +487,7 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa req := newListTeamBindings(sql, &query) q, err := sqltemplate.Execute(sqlQueryTeamBindingsTemplate, req) if err != nil { - return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err) + return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamBindingsTemplate.Name(), err) } rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) @@ -508,7 +509,7 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa for rows.Next() { m := TeamMember{} - err = rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.Created, &m.Updated, &m.Permission) + err = rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Created, &m.Updated, &m.Permission) if err != nil { return res, err } @@ -522,11 +523,95 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa } } - if query.UID == "" { - res.RV, err = sql.GetResourceVersion(ctx, "team_member", "updated") + return res, err +} + +type CreateTeamMemberCommand struct { + TeamID int64 + TeamUID string + UserID int64 + UserUID string + OrgID int64 + Created DBTime + Updated DBTime + External bool + Permission team.PermissionType +} + +type CreateTeamMemberResult struct { + TeamMember TeamMember +} + +var sqlCreateTeamMemberQuery = mustTemplate("create_team_member_query.sql") + +func newCreateTeamMember(sql *legacysql.LegacyDatabaseHelper, cmd *CreateTeamMemberCommand) createTeamMemberQuery { + return createTeamMemberQuery{ + SQLTemplate: sqltemplate.New(sql.DialectForDriver()), + TeamMemberTable: sql.Table("team_member"), + Command: cmd, + } +} + +type createTeamMemberQuery struct { + sqltemplate.SQLTemplate + TeamMemberTable string + Command *CreateTeamMemberCommand +} + +func (r createTeamMemberQuery) Validate() error { + return nil +} + +func (s *legacySQLStore) CreateTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd CreateTeamMemberCommand) (*CreateTeamMemberResult, error) { + now := time.Now().UTC() + cmd.Created = NewDBTime(now) + cmd.Updated = NewDBTime(now) + cmd.OrgID = ns.OrgID + + if cmd.OrgID == 0 { + return nil, fmt.Errorf("expected non zero org id") } - return res, err + sql, err := s.sql(ctx) + if err != nil { + return nil, err + } + + req := newCreateTeamMember(sql, &cmd) + + var createdTeamMember TeamMember + err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error { + teamMemberQuery, err := sqltemplate.Execute(sqlCreateTeamMemberQuery, req) + if err != nil { + return fmt.Errorf("failed to execute team member template %q: %w", sqlCreateTeamMemberQuery.Name(), err) + } + + teamMemberID, err := st.ExecWithReturningId(ctx, teamMemberQuery, req.GetArgs()...) + if err != nil { + return fmt.Errorf("failed to create team member: %w", err) + } + + createdTeamMember = TeamMember{ + ID: teamMemberID, + TeamID: cmd.TeamID, + TeamUID: cmd.TeamUID, + UserID: cmd.UserID, + UserUID: cmd.UserUID, + OrgID: cmd.OrgID, + Created: cmd.Created.Time, + Updated: cmd.Updated.Time, + External: cmd.External, + Permission: cmd.Permission, + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &CreateTeamMemberResult{TeamMember: createdTeamMember}, nil } type ListTeamMembersQuery struct { diff --git a/pkg/registry/apis/iam/legacy/team_bindings_query.sql b/pkg/registry/apis/iam/legacy/team_bindings_query.sql index a64687a7e8f..df8e8118ad5 100644 --- a/pkg/registry/apis/iam/legacy/team_bindings_query.sql +++ b/pkg/registry/apis/iam/legacy/team_bindings_query.sql @@ -1,11 +1,14 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission FROM {{ .Ident .TeamMemberTable }} tm INNER JOIN {{ .Ident .TeamTable }} t ON tm.team_id = t.id INNER JOIN {{ .Ident .UserTable }} u ON tm.user_id = u.id WHERE tm.org_id = {{ .Arg .Query.OrgID}} - {{ if .Query.UID }} - AND t.uid = {{ .Arg .Query.UID }} + {{ if .Query.TeamID }} + AND t.id = {{ .Arg .Query.TeamID }} + {{ end }} + {{ if .Query.UserID }} + AND u.id = {{ .Arg .Query.UserID }} {{ end }} {{- if .Query.Pagination.Continue }} AND tm.id >= {{ .Arg .Query.Pagination.Continue }} diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--create_team_member_query-create_team_member.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--create_team_member_query-create_team_member.sql new file mode 100755 index 00000000000..52ee06990b3 --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--create_team_member_query-create_team_member.sql @@ -0,0 +1,5 @@ +INSERT INTO `grafana`.`team_member` + (team_id, user_id, org_id, created, updated, external, permission) +VALUES + (1, 1, 0, '2023-01-01 12:00:00', + '2023-01-01 12:00:00', FALSE, 'Member') diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--create_team_member_query-create_team_member_admin.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--create_team_member_query-create_team_member_admin.sql new file mode 100755 index 00000000000..e358caa76db --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--create_team_member_query-create_team_member_admin.sql @@ -0,0 +1,5 @@ +INSERT INTO `grafana`.`team_member` + (team_id, user_id, org_id, created, updated, external, permission) +VALUES + (1, 1, 0, '2023-01-01 12:00:00', + '2023-01-01 12:00:00', FALSE, 'Admin') diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_1_bindings.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_1_bindings.sql index 1b3c18c20cc..4a7ad92ba6b 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_1_bindings.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_1_bindings.sql @@ -1,10 +1,11 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission FROM `grafana`.`team_member` tm INNER JOIN `grafana`.`team` t ON tm.team_id = t.id INNER JOIN `grafana`.`user` u ON tm.user_id = u.id WHERE tm.org_id = 1 - AND t.uid = 'team-1' + AND t.uid = 'team1' + AND u.uid = 'user1' AND NOT tm.external ORDER BY t.id ASC LIMIT 1; diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_id.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_id.sql new file mode 100755 index 00000000000..2ecf1da6529 --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_id.sql @@ -0,0 +1,11 @@ +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +FROM `grafana`.`team_member` tm +INNER JOIN `grafana`.`team` t ON tm.team_id = t.id +INNER JOIN `grafana`.`user` u ON tm.user_id = u.id +WHERE + tm.org_id = 1 + AND t.id = 1 + AND u.id = 1 +AND NOT tm.external +ORDER BY t.id ASC +LIMIT 1; diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_1.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_1.sql index 2180553e3a0..b2eaa3a9f42 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_1.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_1.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission FROM `grafana`.`team_member` tm INNER JOIN `grafana`.`team` t ON tm.team_id = t.id INNER JOIN `grafana`.`user` u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_2.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_2.sql index 767f1fe4c82..df9d37bcb2c 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_2.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_2.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission FROM `grafana`.`team_member` tm INNER JOIN `grafana`.`team` t ON tm.team_id = t.id INNER JOIN `grafana`.`user` u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--create_team_member_query-create_team_member.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--create_team_member_query-create_team_member.sql new file mode 100755 index 00000000000..b5cea133905 --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--create_team_member_query-create_team_member.sql @@ -0,0 +1,5 @@ +INSERT INTO "grafana"."team_member" + (team_id, user_id, org_id, created, updated, external, permission) +VALUES + (1, 1, 0, '2023-01-01 12:00:00', + '2023-01-01 12:00:00', FALSE, 'Member') diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--create_team_member_query-create_team_member_admin.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--create_team_member_query-create_team_member_admin.sql new file mode 100755 index 00000000000..d6f128b277b --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--create_team_member_query-create_team_member_admin.sql @@ -0,0 +1,5 @@ +INSERT INTO "grafana"."team_member" + (team_id, user_id, org_id, created, updated, external, permission) +VALUES + (1, 1, 0, '2023-01-01 12:00:00', + '2023-01-01 12:00:00', FALSE, 'Admin') diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_1_bindings.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_1_bindings.sql index 50e968de4a1..5e0568a7b23 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_1_bindings.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_1_bindings.sql @@ -1,10 +1,11 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id WHERE tm.org_id = 1 - AND t.uid = 'team-1' + AND t.uid = 'team1' + AND u.uid = 'user1' AND NOT tm.external ORDER BY t.id ASC LIMIT 1; diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_id.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_id.sql new file mode 100755 index 00000000000..dbabf733606 --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_id.sql @@ -0,0 +1,11 @@ +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +FROM "grafana"."team_member" tm +INNER JOIN "grafana"."team" t ON tm.team_id = t.id +INNER JOIN "grafana"."user" u ON tm.user_id = u.id +WHERE + tm.org_id = 1 + AND t.id = 1 + AND u.id = 1 +AND NOT tm.external +ORDER BY t.id ASC +LIMIT 1; diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_1.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_1.sql index 76f9e5a8cca..2abca6ed298 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_1.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_1.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_2.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_2.sql index c6608dd7968..0a93c590a11 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_2.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_2.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--create_team_member_query-create_team_member.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--create_team_member_query-create_team_member.sql new file mode 100755 index 00000000000..b5cea133905 --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--create_team_member_query-create_team_member.sql @@ -0,0 +1,5 @@ +INSERT INTO "grafana"."team_member" + (team_id, user_id, org_id, created, updated, external, permission) +VALUES + (1, 1, 0, '2023-01-01 12:00:00', + '2023-01-01 12:00:00', FALSE, 'Member') diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--create_team_member_query-create_team_member_admin.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--create_team_member_query-create_team_member_admin.sql new file mode 100755 index 00000000000..d6f128b277b --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--create_team_member_query-create_team_member_admin.sql @@ -0,0 +1,5 @@ +INSERT INTO "grafana"."team_member" + (team_id, user_id, org_id, created, updated, external, permission) +VALUES + (1, 1, 0, '2023-01-01 12:00:00', + '2023-01-01 12:00:00', FALSE, 'Admin') diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_1_bindings.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_1_bindings.sql index 50e968de4a1..5e0568a7b23 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_1_bindings.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_1_bindings.sql @@ -1,10 +1,11 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id WHERE tm.org_id = 1 - AND t.uid = 'team-1' + AND t.uid = 'team1' + AND u.uid = 'user1' AND NOT tm.external ORDER BY t.id ASC LIMIT 1; diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_id.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_id.sql new file mode 100755 index 00000000000..dbabf733606 --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_id.sql @@ -0,0 +1,11 @@ +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +FROM "grafana"."team_member" tm +INNER JOIN "grafana"."team" t ON tm.team_id = t.id +INNER JOIN "grafana"."user" u ON tm.user_id = u.id +WHERE + tm.org_id = 1 + AND t.id = 1 + AND u.id = 1 +AND NOT tm.external +ORDER BY t.id ASC +LIMIT 1; diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_1.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_1.sql index 76f9e5a8cca..2abca6ed298 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_1.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_1.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_2.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_2.sql index c6608dd7968..0a93c590a11 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_2.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_2.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 8c9ad1caae7..e5c1d2b98f2 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -191,7 +191,22 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge } teamBindingResource := iamv0.TeamBindingResourceInfo - storage[teamBindingResource.StoragePath()] = team.NewLegacyBindingStore(b.store) + teamBindingLegacyStore := team.NewLegacyBindingStore(b.store, enableAuthnMutation) + storage[teamBindingResource.StoragePath()] = teamBindingLegacyStore + + if b.enableDualWriter { + teamBindingStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, teamBindingResource, opts.OptsGetter) + if err != nil { + return err + } + + teamBindingDW, err := opts.DualWriteBuilder(teamBindingResource.GroupResource(), teamBindingLegacyStore, teamBindingStore) + if err != nil { + return err + } + + storage[teamBindingResource.StoragePath()] = teamBindingDW + } // User store registration userResource := iamv0.UserResourceInfo @@ -360,6 +375,8 @@ func (b *IdentityAccessManagementAPIBuilder) Validate(ctx context.Context, a adm return serviceaccount.ValidateOnCreate(ctx, typedObj) case *iamv0.Team: return team.ValidateOnCreate(ctx, typedObj) + case *iamv0.TeamBinding: + return team.ValidateOnBindingCreate(ctx, typedObj) case *iamv0.ResourcePermission: return resourcepermission.ValidateCreateAndUpdateInput(ctx, typedObj) } diff --git a/pkg/registry/apis/iam/team/store_binding.go b/pkg/registry/apis/iam/team/store_binding.go index 38b2a23237f..0385b1fa3b3 100644 --- a/pkg/registry/apis/iam/team/store_binding.go +++ b/pkg/registry/apis/iam/team/store_binding.go @@ -2,9 +2,12 @@ package team import ( "context" + "fmt" "strconv" + "strings" "time" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -27,14 +30,19 @@ var ( _ rest.SingularNameProvider = (*LegacyBindingStore)(nil) _ rest.Getter = (*LegacyBindingStore)(nil) _ rest.Lister = (*LegacyBindingStore)(nil) + _ rest.Creater = (*LegacyBindingStore)(nil) + _ rest.Updater = (*LegacyBindingStore)(nil) + _ rest.GracefulDeleter = (*LegacyBindingStore)(nil) + _ rest.CollectionDeleter = (*LegacyBindingStore)(nil) ) -func NewLegacyBindingStore(store legacy.LegacyIdentityStore) *LegacyBindingStore { - return &LegacyBindingStore{store} +func NewLegacyBindingStore(store legacy.LegacyIdentityStore, enableAuthnMutation bool) *LegacyBindingStore { + return &LegacyBindingStore{store, enableAuthnMutation} } type LegacyBindingStore struct { - store legacy.LegacyIdentityStore + store legacy.LegacyIdentityStore + enableAuthnMutation bool } // Destroy implements rest.Storage. @@ -65,6 +73,81 @@ func (l *LegacyBindingStore) ConvertToTable(ctx context.Context, object runtime. return bindingResource.TableConverter().ConvertToTable(ctx, object, tableOptions) } +func (l *LegacyBindingStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "update") +} + +func (l *LegacyBindingStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "delete") +} + +func (l *LegacyBindingStore) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) { + return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "deleteCollection") +} + +func (l *LegacyBindingStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + if !l.enableAuthnMutation { + return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "create") + } + + ns, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, err + } + + teamMemberObj, ok := obj.(*iamv0alpha1.TeamBinding) + if !ok { + return nil, fmt.Errorf("expected TeamBinding object, got %T", obj) + } + + if createValidation != nil { + if err := createValidation(ctx, teamMemberObj); err != nil { + return nil, err + } + } + + // Fetch the user by ID + userObj, err := l.store.GetUserInternalID(ctx, ns, legacy.GetUserInternalIDQuery{ + UID: teamMemberObj.Spec.Subject.Name, + }) + if err != nil { + return nil, fmt.Errorf("failed to fetch user by id %s: %w", teamMemberObj.Spec.Subject.Name, err) + } + + // Fetch the team by ID + teamObj, err := l.store.GetTeamInternalID(ctx, ns, legacy.GetTeamInternalIDQuery{ + UID: teamMemberObj.Spec.TeamRef.Name, + }) + if err != nil { + return nil, fmt.Errorf("failed to fetch team by id %s: %w", teamMemberObj.Spec.TeamRef.Name, err) + } + + var permission team.PermissionType + switch teamMemberObj.Spec.Permission { + case iamv0alpha1.TeamBindingTeamPermissionAdmin: + permission = team.PermissionTypeAdmin + case iamv0alpha1.TeamBindingTeamPermissionMember: + permission = team.PermissionTypeMember + } + + createCmd := legacy.CreateTeamMemberCommand{ + TeamID: teamObj.ID, + TeamUID: teamMemberObj.Spec.TeamRef.Name, + UserID: userObj.ID, + UserUID: teamMemberObj.Spec.Subject.Name, + Permission: permission, + External: false, + } + + result, err := l.store.CreateTeamMember(ctx, ns, createCmd) + if err != nil { + return nil, err + } + + iamTeam := mapToBindingObject(ns, result.TeamMember) + return &iamTeam, nil +} + // Get implements rest.Getter. func (l *LegacyBindingStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) @@ -72,8 +155,11 @@ func (l *LegacyBindingStore) Get(ctx context.Context, name string, options *meta return nil, err } + teamID, userID := mapFromBindingName(name) + res, err := l.store.ListTeamBindings(ctx, ns, legacy.ListTeamBindingsQuery{ - UID: name, + TeamID: teamID, + UserID: userID, Pagination: common.Pagination{Limit: 1}, }) if err != nil { @@ -130,7 +216,7 @@ func mapToBindingObject(ns claims.NamespaceInfo, tm legacy.TeamMember) iamv0alph return iamv0alpha1.TeamBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: tm.TeamUID, + Name: mapToBindingName(tm.TeamID, tm.UserID), Namespace: ns.Value, ResourceVersion: strconv.FormatInt(rv.UnixMilli(), 10), CreationTimestamp: metav1.NewTime(ct), @@ -147,6 +233,33 @@ func mapToBindingObject(ns claims.NamespaceInfo, tm legacy.TeamMember) iamv0alph } } +func mapToBindingName(teamID, userID int64) string { + return fmt.Sprintf("teambinding-%d-%d", teamID, userID) +} + +func mapFromBindingName(name string) (int64, int64) { + parts := strings.Split(name, "-") + if len(parts) != 3 { + return 0, 0 + } + + if parts[0] != "teambinding" { + return 0, 0 + } + + teamID, err := strconv.ParseInt(parts[1], 10, 64) + if err != nil { + return 0, 0 + } + + userID, err := strconv.ParseInt(parts[2], 10, 64) + if err != nil { + return 0, 0 + } + + return teamID, userID +} + func mapPermisson(p team.PermissionType) iamv0.TeamPermission { if p == team.PermissionTypeAdmin { return iamv0.TeamPermissionAdmin diff --git a/pkg/registry/apis/iam/team/validate.go b/pkg/registry/apis/iam/team/validate.go index 97777cb3c4e..6859310e402 100644 --- a/pkg/registry/apis/iam/team/validate.go +++ b/pkg/registry/apis/iam/team/validate.go @@ -55,3 +55,24 @@ func ValidateOnUpdate(ctx context.Context, obj, old *iamv0alpha1.Team) error { return nil } + +func ValidateOnBindingCreate(ctx context.Context, obj *iamv0alpha1.TeamBinding) error { + _, err := identity.GetRequester(ctx) + if err != nil { + return apierrors.NewUnauthorized("no identity found") + } + + if obj.Spec.Permission != iamv0alpha1.TeamBindingTeamPermissionAdmin && obj.Spec.Permission != iamv0alpha1.TeamBindingTeamPermissionMember { + return apierrors.NewBadRequest("invalid permission") + } + + if obj.Spec.Subject.Name == "" { + return apierrors.NewBadRequest("subject is required") + } + + if obj.Spec.TeamRef.Name == "" { + return apierrors.NewBadRequest("teamRef is required") + } + + return nil +} diff --git a/pkg/registry/apis/iam/team/validate_test.go b/pkg/registry/apis/iam/team/validate_test.go index 1093a01f57d..e4a7e0bf53b 100644 --- a/pkg/registry/apis/iam/team/validate_test.go +++ b/pkg/registry/apis/iam/team/validate_test.go @@ -325,3 +325,113 @@ func TestValidateOnUpdate(t *testing.T) { }) } } + +func TestValidateOnBindingCreate(t *testing.T) { + tests := []struct { + name string + requester *identity.StaticRequester + obj *iamv0alpha1.TeamBinding + want error + }{ + { + name: "valid team binding create", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: nil, + }, + { + name: "invalid team binding - invalid permission", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: "invalid", + }, + }, + want: apierrors.NewBadRequest("invalid permission"), + }, + { + name: "invalid team binding - no subject", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: apierrors.NewBadRequest("subject is required"), + }, + { + name: "invalid team binding - no teamRef", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: apierrors.NewBadRequest("teamRef is required"), + }, + { + name: "invalid team binding - no requester in context", + requester: nil, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: apierrors.NewUnauthorized("no identity found"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := identity.WithRequester(context.Background(), test.requester) + err := ValidateOnBindingCreate(ctx, test.obj) + assert.Equal(t, test.want, err) + }) + } +} diff --git a/pkg/tests/apis/iam/iam_test.go b/pkg/tests/apis/iam/iam_test.go index ffd7dedf77c..f49fd19f349 100644 --- a/pkg/tests/apis/iam/iam_test.go +++ b/pkg/tests/apis/iam/iam_test.go @@ -27,6 +27,12 @@ var gvrUsers = schema.GroupVersionResource{ Resource: "users", } +var gvrTeamBindings = schema.GroupVersionResource{ + Group: "iam.grafana.app", + Version: "v0alpha1", + Resource: "teambindings", +} + func TestMain(m *testing.M) { testsuite.Run(m) } diff --git a/pkg/tests/apis/iam/team_bindings_integration_test.go b/pkg/tests/apis/iam/team_bindings_integration_test.go new file mode 100644 index 00000000000..606cc629faf --- /dev/null +++ b/pkg/tests/apis/iam/team_bindings_integration_test.go @@ -0,0 +1,295 @@ +package identity + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/grafana/grafana/pkg/apiserver/rest" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tests/apis" + "github.com/grafana/grafana/pkg/tests/testinfra" + "github.com/grafana/grafana/pkg/util/testutil" +) + +func TestIntegrationTeamBindings(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + // TODO: Add rest.Mode4 when it's supported + modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3} + for _, mode := range modes { + t.Run(fmt.Sprintf("Team binding CRUD operations with dual writer mode %d", mode), func(t *testing.T) { + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: false, + DisableAnonymous: true, + APIServerStorageType: "unified", + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "teambindings.iam.grafana.app": { + DualWriterMode: mode, + }, + }, + EnableFeatureToggles: []string{ + featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, + featuremgmt.FlagKubernetesAuthnMutation, + }, + }) + + ctx := context.Background() + + // Create a team + teamClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrTeams, + }) + + team, err := teamClient.Resource.Create(ctx, helper.LoadYAMLOrJSONFile("testdata/team-test-create-v0.yaml"), metav1.CreateOptions{}) + require.NoError(t, err) + require.NotNil(t, team) + + // Create a user + userClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrUsers, + }) + + user, err := userClient.Resource.Create(ctx, helper.LoadYAMLOrJSONFile("testdata/user-test-create-v0.yaml"), metav1.CreateOptions{}) + require.NoError(t, err) + require.NotNil(t, user) + + doTeamBindingCRUDTestsUsingTheNewAPIs(t, helper, team, user) + + if mode < 3 { + doTeamBindingCRUDTestsUsingTheLegacyAPIs(t, helper, mode) + } + }) + } +} + +func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHelper, team *unstructured.Unstructured, user *unstructured.Unstructured) { + t.Run("should create/get team binding using the new APIs", func(t *testing.T) { + ctx := context.Background() + + teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrTeamBindings, + }) + + // Create the team binding + toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml") + toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName() + toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName() + created, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{}) + require.NoError(t, err) + require.NotNil(t, created) + + createdSpec := created.Object["spec"].(map[string]interface{}) + require.Equal(t, user.GetName(), createdSpec["subject"].(map[string]interface{})["name"]) + require.Equal(t, team.GetName(), createdSpec["teamRef"].(map[string]interface{})["name"]) + require.Equal(t, "admin", createdSpec["permission"]) + + createdUID := created.GetName() + require.NotEmpty(t, createdUID) + + // Get the team binding + fetched, err := teamBindingClient.Resource.Get(ctx, createdUID, metav1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, fetched) + + fetchedSpec := fetched.Object["spec"].(map[string]interface{}) + require.Equal(t, user.GetName(), fetchedSpec["subject"].(map[string]interface{})["name"]) + require.Equal(t, team.GetName(), fetchedSpec["teamRef"].(map[string]interface{})["name"]) + require.Equal(t, "admin", fetchedSpec["permission"]) + + require.Equal(t, createdUID, fetched.GetName()) + require.Equal(t, "default", fetched.GetNamespace()) + }) + + t.Run("should not be able to create team binding when using a user with insufficient permissions", func(t *testing.T) { + for _, u := range []apis.User{ + helper.Org1.Editor, + helper.Org1.Viewer, + } { + t.Run(fmt.Sprintf("with basic role_%s", u.Identity.GetOrgRole()), func(t *testing.T) { + ctx := context.Background() + teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: u, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrTeamBindings, + }) + + toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml") + toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName() + toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName() + _, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{}) + require.Error(t, err) + + var statusErr *errors.StatusError + require.ErrorAs(t, err, &statusErr) + require.Equal(t, int32(403), statusErr.ErrStatus.Code) + }) + } + }) + + t.Run("should not be able to create team binding without a subject", func(t *testing.T) { + ctx := context.Background() + teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrTeamBindings, + }) + + toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml") + toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = "" + toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName() + + _, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{}) + require.Error(t, err) + var statusErr *errors.StatusError + require.ErrorAs(t, err, &statusErr) + require.Equal(t, int32(400), statusErr.ErrStatus.Code) + require.Contains(t, statusErr.ErrStatus.Message, "subject is required") + }) + + t.Run("should not be able to create team binding without a teamRef", func(t *testing.T) { + ctx := context.Background() + teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrTeamBindings, + }) + + toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml") + toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName() + toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = "" + + _, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{}) + require.Error(t, err) + var statusErr *errors.StatusError + require.ErrorAs(t, err, &statusErr) + require.Equal(t, int32(400), statusErr.ErrStatus.Code) + require.Contains(t, statusErr.ErrStatus.Message, "teamRef is required") + }) + + t.Run("should not be able to create team binding with invalid permission", func(t *testing.T) { + ctx := context.Background() + teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrTeamBindings, + }) + + toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml") + toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName() + toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName() + toCreate.Object["spec"].(map[string]interface{})["permission"] = "invalid" + + _, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{}) + require.Error(t, err) + var statusErr *errors.StatusError + require.ErrorAs(t, err, &statusErr) + require.Equal(t, int32(400), statusErr.ErrStatus.Code) + require.Contains(t, statusErr.ErrStatus.Message, "invalid permission") + }) +} + +func doTeamBindingCRUDTestsUsingTheLegacyAPIs(t *testing.T, helper *apis.K8sTestHelper, mode rest.DualWriterMode) { + t.Run("should create team binding using legacy APIs and get it using the new APIs", func(t *testing.T) { + ctx := context.Background() + + // Create a team using legacy API + legacyTeamPayload := `{ + "name": "Test Team Legacy", + "email": "testteamlegacy@example.com" + }` + + type legacyTeamResponse struct { + UID string `json:"uid"` + ID int64 `json:"teamId"` + } + + teamRsp := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "POST", + Path: "/api/teams", + Body: []byte(legacyTeamPayload), + }, &legacyTeamResponse{}) + + require.NotNil(t, teamRsp) + require.Equal(t, 200, teamRsp.Response.StatusCode) + require.NotEmpty(t, teamRsp.Result.UID) + + // Create a user using legacy API + legacyUserPayload := `{ + "name": "Test User 2", + "email": "testuser2@example.com", + "login": "testuser2", + "password": "password123" + }` + + type legacyUserResponse struct { + UID string `json:"uid"` + ID int64 `json:"id"` + } + + userRsp := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "POST", + Path: "/api/admin/users", + Body: []byte(legacyUserPayload), + }, &legacyUserResponse{}) + + require.NotNil(t, userRsp) + require.Equal(t, 200, userRsp.Response.StatusCode) + require.NotEmpty(t, userRsp.Result.UID) + + // Create team binding using legacy API + legacyTeamBindingPayload := `{ + "userId": ` + fmt.Sprintf("%d", userRsp.Result.ID) + `, + "teamId": ` + fmt.Sprintf("%d", teamRsp.Result.ID) + `, + "permission": "member" + }` + + type legacyTeamBindingResponse struct { + UserID int64 `json:"userId"` + TeamID int64 `json:"teamId"` + Permission string `json:"permission"` + } + + teamBindingRsp := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "POST", + Path: "/api/teams/" + teamRsp.Result.UID + "/members", + Body: []byte(legacyTeamBindingPayload), + }, &legacyTeamBindingResponse{}) + + require.NotNil(t, teamBindingRsp) + require.Equal(t, 200, teamBindingRsp.Response.StatusCode) + + // Get team binding using new API + teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrTeamBindings, + }) + + teamBindingName := fmt.Sprintf("teambinding-%d-%d", teamRsp.Result.ID, userRsp.Result.ID) + teamBinding, err := teamBindingClient.Resource.Get(ctx, teamBindingName, metav1.GetOptions{}) + require.NoError(t, err) + require.NotNil(t, teamBinding) + + teamBindingSpec := teamBinding.Object["spec"].(map[string]interface{}) + require.Equal(t, "member", teamBindingSpec["permission"]) + require.Equal(t, userRsp.Result.UID, teamBindingSpec["subject"].(map[string]interface{})["name"]) + require.Equal(t, teamRsp.Result.UID, teamBindingSpec["teamRef"].(map[string]interface{})["name"]) + require.Equal(t, teamBindingName, teamBinding.GetName()) + }) +} diff --git a/pkg/tests/apis/iam/testdata/team-test-create-v0.yaml b/pkg/tests/apis/iam/testdata/team-test-create-v0.yaml index b04aee4c104..f3e987111fe 100644 --- a/pkg/tests/apis/iam/testdata/team-test-create-v0.yaml +++ b/pkg/tests/apis/iam/testdata/team-test-create-v0.yaml @@ -1,7 +1,7 @@ apiVersion: iam.grafana.app/v0alpha1 kind: Team metadata: - name: test-team-1 + name: testteam1 spec: title: "Test Team 1" email: testteam1@example123.com diff --git a/pkg/tests/apis/iam/testdata/team-test-update-v0.yaml b/pkg/tests/apis/iam/testdata/team-test-update-v0.yaml index 0b01271cb3f..fbb19cdc559 100644 --- a/pkg/tests/apis/iam/testdata/team-test-update-v0.yaml +++ b/pkg/tests/apis/iam/testdata/team-test-update-v0.yaml @@ -1,7 +1,7 @@ apiVersion: iam.grafana.app/v0alpha1 kind: Team metadata: - name: test-team-1 + name: testteam1 spec: title: "Test Team 2" email: testteam2@example123.com diff --git a/pkg/tests/apis/iam/testdata/teambinding-test-create-v0.yaml b/pkg/tests/apis/iam/testdata/teambinding-test-create-v0.yaml new file mode 100644 index 00000000000..9baf0893499 --- /dev/null +++ b/pkg/tests/apis/iam/testdata/teambinding-test-create-v0.yaml @@ -0,0 +1,10 @@ +apiVersion: iam.grafana.app/v0alpha1 +kind: TeamBinding +metadata: + name: test-team-binding-1 +spec: + subject: + name: "" + teamRef: + name: "" + permission: "admin" diff --git a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json index 3561b54c453..856062877c3 100644 --- a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json @@ -1518,6 +1518,98 @@ ], "description": "list objects of kind TeamBinding", "operationId": "listTeamBinding", + "parameters": [ + { + "name": "allowWatchBookmarks", + "in": "query", + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "continue", + "in": "query", + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "labelSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "limit", + "in": "query", + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "resourceVersion", + "in": "query", + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "resourceVersionMatch", + "in": "query", + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "sendInitialEvents", + "in": "query", + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "timeoutSeconds", + "in": "query", + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "watch", + "in": "query", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], "responses": { "200": { "description": "OK", @@ -1557,52 +1649,285 @@ "kind": "TeamBinding" } }, + "post": { + "tags": [ + "TeamBinding" + ], + "description": "create a TeamBinding", + "operationId": "createTeamBinding", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + } + } + }, + "202": { + "description": "Accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + } + } + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "iam.grafana.app", + "version": "v0alpha1", + "kind": "TeamBinding" + } + }, + "delete": { + "tags": [ + "TeamBinding" + ], + "description": "delete collection of TeamBinding", + "operationId": "deletecollectionTeamBinding", + "parameters": [ + { + "name": "continue", + "in": "query", + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "gracePeriodSeconds", + "in": "query", + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "in": "query", + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "labelSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "limit", + "in": "query", + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "orphanDependents", + "in": "query", + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "propagationPolicy", + "in": "query", + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "resourceVersion", + "in": "query", + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "resourceVersionMatch", + "in": "query", + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "sendInitialEvents", + "in": "query", + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "timeoutSeconds", + "in": "query", + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "schema": { + "type": "integer", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + } + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "iam.grafana.app", + "version": "v0alpha1", + "kind": "TeamBinding" + } + }, "parameters": [ - { - "name": "allowWatchBookmarks", - "in": "query", - "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "continue", - "in": "query", - "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldSelector", - "in": "query", - "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "labelSelector", - "in": "query", - "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "limit", - "in": "query", - "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, { "name": "namespace", "in": "path", @@ -1621,51 +1946,6 @@ "type": "string", "uniqueItems": true } - }, - { - "name": "resourceVersion", - "in": "query", - "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "resourceVersionMatch", - "in": "query", - "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "sendInitialEvents", - "in": "query", - "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - }, - { - "name": "timeoutSeconds", - "in": "query", - "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", - "schema": { - "type": "integer", - "uniqueItems": true - } - }, - { - "name": "watch", - "in": "query", - "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", - "schema": { - "type": "boolean", - "uniqueItems": true - } } ] }, @@ -1705,6 +1985,330 @@ "kind": "TeamBinding" } }, + "put": { + "tags": [ + "TeamBinding" + ], + "description": "replace the specified TeamBinding", + "operationId": "replaceTeamBinding", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + } + } + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "iam.grafana.app", + "version": "v0alpha1", + "kind": "TeamBinding" + } + }, + "delete": { + "tags": [ + "TeamBinding" + ], + "description": "delete a TeamBinding", + "operationId": "deleteTeamBinding", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "gracePeriodSeconds", + "in": "query", + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "in": "query", + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "orphanDependents", + "in": "query", + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "propagationPolicy", + "in": "query", + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + } + }, + "202": { + "description": "Accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + } + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "iam.grafana.app", + "version": "v0alpha1", + "kind": "TeamBinding" + } + }, + "patch": { + "tags": [ + "TeamBinding" + ], + "description": "partially update the specified TeamBinding", + "operationId": "updateTeamBinding", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "force", + "in": "query", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.TeamBinding" + } + } + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "iam.grafana.app", + "version": "v0alpha1", + "kind": "TeamBinding" + } + }, "parameters": [ { "name": "name", From ab4bacbead4a9756ee57bbf6cc216eb3aad3fef0 Mon Sep 17 00:00:00 2001 From: Will Browne <wbrowne@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:25:04 +0100 Subject: [PATCH 09/75] Plugins: Pass image tag separately for plugin container mode (#112344) propagate tag --- pkg/plugins/backendplugin/grpcplugin/client.go | 8 +++++--- pkg/plugins/backendplugin/grpcplugin/client_proto.go | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/plugins/backendplugin/grpcplugin/client.go b/pkg/plugins/backendplugin/grpcplugin/client.go index b60272d3e58..19b345229c9 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client.go +++ b/pkg/plugins/backendplugin/grpcplugin/client.go @@ -63,7 +63,7 @@ func newClientConfig(descriptor PluginDescriptor, env []string, logger log.Logge versionedPlugins := descriptor.versionedPlugins if runtime.GOOS == "linux" && descriptor.containerMode.enabled { - return containerClientConfig(executablePath, descriptor.containerMode.image, logger, versionedPlugins, skipHostEnvVars, tracer) + return containerClientConfig(executablePath, descriptor.containerMode.image, descriptor.containerMode.tag, logger, versionedPlugins, skipHostEnvVars, tracer) } logger.Debug("Using process mode", "os", runtime.GOOS, "executablePath", executablePath) @@ -92,13 +92,14 @@ func newClientConfig(descriptor PluginDescriptor, env []string, logger log.Logge } } -func containerClientConfig(executablePath, containerImage string, logger log.Logger, versionedPlugins map[int]goplugin.PluginSet, skipHostEnvVars bool, tracer trace.Tracer) *goplugin.ClientConfig { - logger.Info("Using container mode", "executable", executablePath, "image", containerImage) +func containerClientConfig(executablePath, containerImage, containerTag string, logger log.Logger, versionedPlugins map[int]goplugin.PluginSet, skipHostEnvVars bool, tracer trace.Tracer) *goplugin.ClientConfig { + logger.Info("Using container mode", "executable", executablePath, "image", containerImage, "tag", containerTag) return &goplugin.ClientConfig{ RunnerFunc: func(l hclog.Logger, cmd *exec.Cmd, tmpDir string) (runner.Runner, error) { logger.Info("Creating container runner", "executablePath", executablePath, "tmpDir", tmpDir) config := &plugincontainer.Config{ Image: containerImage, + Tag: containerTag, Env: cmd.Env, } @@ -133,6 +134,7 @@ type PluginDescriptor struct { type containerModeOpts struct { enabled bool image string + tag string } // NewBackendPlugin creates a new backend plugin factory used for registering a backend plugin. diff --git a/pkg/plugins/backendplugin/grpcplugin/client_proto.go b/pkg/plugins/backendplugin/grpcplugin/client_proto.go index 65920e25cc9..555cd8f3a59 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client_proto.go +++ b/pkg/plugins/backendplugin/grpcplugin/client_proto.go @@ -57,6 +57,7 @@ type ProtoClientOpts struct { type ContainerModeOpts struct { Enabled bool Image string + Tag string } func NewProtoClient(opts ProtoClientOpts) (ProtoClient, error) { @@ -70,6 +71,7 @@ func NewProtoClient(opts ProtoClientOpts) (ProtoClient, error) { containerMode: containerModeOpts{ enabled: opts.ContainerMode.Enabled, image: opts.ContainerMode.Image, + tag: opts.ContainerMode.Tag, }, skipHostEnvVars: opts.SkipHostEnvVars, }, From 3bb75e2a90183ce3500f4fae50fc85bf8ca9cbb4 Mon Sep 17 00:00:00 2001 From: Georges Chaudy <chaudyg@gmail.com> Date: Tue, 14 Oct 2025 17:19:26 +0200 Subject: [PATCH 10/75] kvstore: Add BatchGet (#111594) * Add BatchGet * restructure the batch get tests --- pkg/storage/unified/resource/kv.go | 61 ++++++++++++ pkg/storage/unified/testing/kv.go | 146 +++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+) diff --git a/pkg/storage/unified/resource/kv.go b/pkg/storage/unified/resource/kv.go index a4215a1570a..8700c9538aa 100644 --- a/pkg/storage/unified/resource/kv.go +++ b/pkg/storage/unified/resource/kv.go @@ -15,6 +15,12 @@ import ( var ErrNotFound = errors.New("key not found") +// KeyValue represents a key-value pair returned by BatchGet +type KeyValue struct { + Key string + Value io.ReadCloser +} + type SortOrder int const ( @@ -36,6 +42,11 @@ type KV interface { // Get retrieves the value for a key from the store Get(ctx context.Context, section string, key string) (io.ReadCloser, error) + // BatchGet retrieves multiple values for the given keys from the store. + // Non-existent entries will not appear in the result. + // The order of the keys is retained in the result. + BatchGet(ctx context.Context, section string, keys []string) iter.Seq2[KeyValue, error] + // Save a new value - returns a WriteCloser to write the value to Save(ctx context.Context, section string, key string) (io.WriteCloser, error) @@ -92,6 +103,56 @@ func (k *badgerKV) Get(ctx context.Context, section string, key string) (io.Read return io.NopCloser(bytes.NewReader(value)), nil } +func (k *badgerKV) BatchGet(ctx context.Context, section string, keys []string) iter.Seq2[KeyValue, error] { + if k.db.IsClosed() { + return func(yield func(KeyValue, error) bool) { + yield(KeyValue{}, fmt.Errorf("database is closed")) + } + } + + if section == "" { + return func(yield func(KeyValue, error) bool) { + yield(KeyValue{}, fmt.Errorf("section is required")) + } + } + + return func(yield func(KeyValue, error) bool) { + txn := k.db.NewTransaction(false) + defer txn.Discard() + + for _, key := range keys { + keyWithSection := section + "/" + key + + item, err := txn.Get([]byte(keyWithSection)) + if err != nil { + if errors.Is(err, badger.ErrKeyNotFound) { + // Skip non-existent keys as per the requirement + continue + } + // For other errors, yield the error and stop + yield(KeyValue{}, err) + return + } + + // Get the value and create a reader from it + value, err := item.ValueCopy(nil) + if err != nil { + yield(KeyValue{}, err) + return + } + + kv := KeyValue{ + Key: key, + Value: io.NopCloser(bytes.NewReader(value)), + } + + if !yield(kv, nil) { + return + } + } + } +} + // badgerWriteCloser implements io.WriteCloser for badgerKV type badgerWriteCloser struct { db *badger.DB diff --git a/pkg/storage/unified/testing/kv.go b/pkg/storage/unified/testing/kv.go index 79704b48b55..00f5a43e5e3 100644 --- a/pkg/storage/unified/testing/kv.go +++ b/pkg/storage/unified/testing/kv.go @@ -26,6 +26,7 @@ const ( TestKVKeysWithSort = "keys with sorting" TestKVConcurrent = "concurrent operations" TestKVUnixTimestamp = "unix timestamp" + TestKVBatchGet = "batch get operations" ) // NewKVFunc is a function that creates a new KV instance for testing @@ -65,6 +66,7 @@ func RunKVTest(t *testing.T, newKV NewKVFunc, opts *KVTestOptions) { {TestKVKeysWithSort, runTestKVKeysWithSort}, {TestKVConcurrent, runTestKVConcurrent}, {TestKVUnixTimestamp, runTestKVUnixTimestamp}, + {TestKVBatchGet, runTestKVBatchGet}, } for _, tc := range cases { @@ -527,6 +529,150 @@ func runTestKVUnixTimestamp(t *testing.T, kv resource.KV, nsPrefix string) { }) } +func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { + ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + section := nsPrefix + "-batchget" + + t.Run("batch get existing keys", func(t *testing.T) { + // Setup test data + testData := map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + } + + // Save test data + for key, value := range testData { + saveKVHelper(t, kv, ctx, section, key, strings.NewReader(value)) + } + + // Batch get all keys + keys := []string{"key1", "key2", "key3"} + type result struct { + key string + value string + } + var results []result + for kv, err := range kv.BatchGet(ctx, section, keys) { + require.NoError(t, err) + value, err := io.ReadAll(kv.Value) + require.NoError(t, err) + err = kv.Value.Close() + require.NoError(t, err) + results = append(results, result{key: kv.Key, value: string(value)}) + } + + // Verify results + assert.Len(t, results, 3) + + // Check that all keys are present and in order + expectedKeys := []string{"key1", "key2", "key3"} + actualKeys := make([]string, len(results)) + for i, r := range results { + actualKeys[i] = r.key + } + assert.Equal(t, expectedKeys, actualKeys) + + // Verify values + for _, r := range results { + assert.Equal(t, testData[r.key], r.value) + } + }) + + t.Run("batch get with non-existent keys", func(t *testing.T) { + // Setup some test data + saveKVHelper(t, kv, ctx, section, "existing-key", strings.NewReader("existing-value")) + + // Batch get with mix of existing and non-existent keys + keys := []string{"existing-key", "non-existent-1", "non-existent-2"} + type result struct { + key string + value string + } + var results []result + for kv, err := range kv.BatchGet(ctx, section, keys) { + require.NoError(t, err) + value, err := io.ReadAll(kv.Value) + require.NoError(t, err) + err = kv.Value.Close() + require.NoError(t, err) + results = append(results, result{key: kv.Key, value: string(value)}) + } + + // Should only return the existing key + assert.Len(t, results, 1) + assert.Equal(t, "existing-key", results[0].key) + assert.Equal(t, "existing-value", results[0].value) + }) + + t.Run("batch get with all non-existent keys", func(t *testing.T) { + keys := []string{"non-existent-1", "non-existent-2", "non-existent-3"} + var results []resource.KeyValue + for kv, err := range kv.BatchGet(ctx, section, keys) { + require.NoError(t, err) + results = append(results, kv) + } + + // Should return no results + assert.Empty(t, results) + }) + + t.Run("batch get with empty keys list", func(t *testing.T) { + keys := []string{} + var results []resource.KeyValue + for kv, err := range kv.BatchGet(ctx, section, keys) { + require.NoError(t, err) + results = append(results, kv) + } + + // Should return no results + assert.Empty(t, results) + }) + + t.Run("batch get with empty section", func(t *testing.T) { + keys := []string{"some-key"} + var errors []error + for kv, err := range kv.BatchGet(ctx, "", keys) { + if err != nil { + errors = append(errors, err) + break + } + _ = kv // unused + } + assert.Len(t, errors, 1) + assert.Contains(t, errors[0].Error(), "section is required") + }) + + t.Run("batch get preserves order", func(t *testing.T) { + // Setup test data + testData := map[string]string{ + "z-key": "z-value", + "a-key": "a-value", + "m-key": "m-value", + } + + // Save test data + for key, value := range testData { + saveKVHelper(t, kv, ctx, section, key, strings.NewReader(value)) + } + + // Batch get in specific order + keys := []string{"z-key", "a-key", "m-key"} + var results []string + for kv, err := range kv.BatchGet(ctx, section, keys) { + require.NoError(t, err) + err = kv.Value.Close() + require.NoError(t, err) + results = append(results, kv.Key) + } + + // Verify order is preserved + assert.Len(t, results, 3) + expectedOrder := []string{"z-key", "a-key", "m-key"} + assert.Equal(t, expectedOrder, results) + }) +} + // saveKVHelper is a helper function to save data to KV store using the new WriteCloser interface func saveKVHelper(t *testing.T, kv resource.KV, ctx context.Context, section, key string, value io.Reader) { t.Helper() From 75a1846344ec598d38ad7421ff636c69d1162df6 Mon Sep 17 00:00:00 2001 From: maicon <maiconscosta@gmail.com> Date: Tue, 14 Oct 2025 12:45:27 -0300 Subject: [PATCH 11/75] Annotations: Honor dashboardUID on dashboardsWithVisibleAnnotations (#112350) * Annotations: Honor dashboardUID on dashboardsWithVisibleAnnotations Signed-off-by: Maicon Costa <maiconscosta@gmail.com> --------- Signed-off-by: Maicon Costa <maiconscosta@gmail.com> --- .../accesscontrol/accesscontrol.go | 15 ++- .../accesscontrol/accesscontrol_test.go | 109 ++++++++++++++++++ 2 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 pkg/services/annotations/accesscontrol/accesscontrol_test.go diff --git a/pkg/services/annotations/accesscontrol/accesscontrol.go b/pkg/services/annotations/accesscontrol/accesscontrol.go index ac9fed6ee18..1627469f213 100644 --- a/pkg/services/annotations/accesscontrol/accesscontrol.go +++ b/pkg/services/annotations/accesscontrol/accesscontrol.go @@ -131,19 +131,22 @@ func (authz *AuthService) dashboardsWithVisibleAnnotations(ctx context.Context, searchstore.OrgFilter{OrgId: query.OrgID}, } + var dashboardUIDs []string if query.DashboardUID != "" { + dashboardUIDs = append(dashboardUIDs, query.DashboardUID) filters = append(filters, searchstore.DashboardFilter{ UIDs: []string{query.DashboardUID}, }) } dashs, err := authz.dashSvc.SearchDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{ - OrgId: query.SignedInUser.GetOrgID(), - Filters: filters, - SignedInUser: query.SignedInUser, - Page: query.Page, - Type: filterType, - Limit: authz.searchDashboardsPageLimit, + DashboardUIDs: dashboardUIDs, + OrgId: query.SignedInUser.GetOrgID(), + Filters: filters, + SignedInUser: query.SignedInUser, + Page: query.Page, + Type: filterType, + Limit: authz.searchDashboardsPageLimit, }) if err != nil { return nil, err diff --git a/pkg/services/annotations/accesscontrol/accesscontrol_test.go b/pkg/services/annotations/accesscontrol/accesscontrol_test.go new file mode 100644 index 00000000000..bbe42788d11 --- /dev/null +++ b/pkg/services/annotations/accesscontrol/accesscontrol_test.go @@ -0,0 +1,109 @@ +package accesscontrol + +import ( + "context" + "testing" + + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/annotations" + "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/search/model" + "github.com/grafana/grafana/pkg/services/sqlstore/permissions" + "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" + "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/tests/testsuite" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestMain(m *testing.M) { + testsuite.Run(m) +} +func TestDashboardsWithVisibleAnnotations(t *testing.T) { + store := db.InitTestDB(t) + + user := &user.SignedInUser{ + OrgID: 1, + } + + // Create permission filters + p1 := permissions.NewAccessControlDashboardPermissionFilter(user, dashboardaccess.PERMISSION_VIEW, searchstore.TypeDashboard, featuremgmt.WithFeatures(), true, store.GetDialect()) + p2 := searchstore.OrgFilter{OrgId: 1} + + // If DashboardUID is provided, it should be added as a filter + p3 := searchstore.DashboardFilter{UIDs: []string{"uid1"}} + + dashSvc := &dashboards.FakeDashboardService{} + + // First call, without DashboardUID + queryNoDashboardUID := &dashboards.FindPersistedDashboardsQuery{ + OrgId: 1, + SignedInUser: user, + Type: "dash-db", + Limit: int64(100), + Page: int64(1), + Filters: []any{ + p1, + p2, + }, + } + dashSvc.On("SearchDashboards", mock.Anything, queryNoDashboardUID).Return(model.HitList{ + &model.Hit{UID: "uid1", ID: 101}, + &model.Hit{UID: "uid2", ID: 102}, + }, nil) + + // Second call, with DashboardUID filter + queryWithDashboardUID := &dashboards.FindPersistedDashboardsQuery{ + OrgId: 1, + SignedInUser: user, + Type: "dash-db", + Limit: int64(100), + Page: int64(1), + Filters: []any{ + p1, + p2, + // This filter should be added on second call + p3, + }, + DashboardUIDs: []string{"uid1"}, + } + + dashSvc.On("SearchDashboards", mock.Anything, queryWithDashboardUID).Return(model.HitList{ + &model.Hit{UID: "uid1", ID: 101}, + }, nil) + + // Create auth service + authz := &AuthService{ + db: store, + features: featuremgmt.WithFeatures(), + dashSvc: dashSvc, + searchDashboardsPageLimit: 100, + } + + // First call without DashboardUID + result, err := authz.dashboardsWithVisibleAnnotations(context.Background(), annotations.ItemQuery{ + SignedInUser: user, + OrgID: 1, + Page: 1, + }) + assert.NoError(t, err) + // Should return two dashboards + assert.Equal(t, map[string]int64{"uid1": 101, "uid2": 102}, result) + // Ensure SearchDashboards was called with correct query + dashSvc.AssertCalled(t, "SearchDashboards", mock.Anything, queryNoDashboardUID) + + // Second call with DashboardUID + result, err = authz.dashboardsWithVisibleAnnotations(context.Background(), annotations.ItemQuery{ + SignedInUser: user, + OrgID: 1, + Page: 1, + DashboardUID: "uid1", + }) + assert.NoError(t, err) + // Should only return one dashboard + assert.Equal(t, map[string]int64{"uid1": 101}, result) + // Ensure SearchDashboards was called with correct query (including DashboardUID filter) + dashSvc.AssertCalled(t, "SearchDashboards", mock.Anything, queryWithDashboardUID) +} From 507720e60106c7f3d1bdf4e0d5852d60db2cc8bf Mon Sep 17 00:00:00 2001 From: Hugo Kiyodi Oshiro <hugo.oshiro@grafana.com> Date: Tue, 14 Oct 2025 15:34:33 -0300 Subject: [PATCH 12/75] Connections: Fix connections home page on enterprise (#111751) --- public/app/features/connections/Connections.test.tsx | 5 ++--- .../app/features/connections/pages/ConnectionsHomePage.tsx | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/public/app/features/connections/Connections.test.tsx b/public/app/features/connections/Connections.test.tsx index 1b92b5dfd1c..27a9fa9373e 100644 --- a/public/app/features/connections/Connections.test.tsx +++ b/public/app/features/connections/Connections.test.tsx @@ -2,7 +2,6 @@ import { RenderResult, screen } from '@testing-library/react'; import { Route, Routes } from 'react-router-dom-v5-compat'; import { render } from 'test/test-utils'; -import { GrafanaEdition } from '@grafana/data/internal'; import { config } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; import * as api from 'app/features/datasources/api'; @@ -46,7 +45,7 @@ describe('Connections', () => { }); test('shows the "Connections Homepage" page by default when edition is Cloud', async () => { - config.buildInfo.edition = GrafanaEdition.Enterprise; + config.pluginAdminExternalManageEnabled = true; renderPage(); // Add new connection card @@ -66,7 +65,7 @@ describe('Connections', () => { }); test('shows the OSS "Connections Homepage" page by default when edition is OpenSource', async () => { - config.buildInfo.edition = GrafanaEdition.OpenSource; + config.pluginAdminExternalManageEnabled = false; renderPage(); // Add new connection card diff --git a/public/app/features/connections/pages/ConnectionsHomePage.tsx b/public/app/features/connections/pages/ConnectionsHomePage.tsx index 647c3acfb50..46e5093cd0a 100644 --- a/public/app/features/connections/pages/ConnectionsHomePage.tsx +++ b/public/app/features/connections/pages/ConnectionsHomePage.tsx @@ -1,7 +1,6 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; -import { GrafanaEdition } from '@grafana/data/internal'; import { Trans } from '@grafana/i18n'; import { config } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; @@ -13,9 +12,9 @@ import PageCard from '../components/PageCard/PageCard'; export default function ConnectionsHomePage() { const styles = useStyles2(getStyles); - const isOSS = config.buildInfo.edition === GrafanaEdition.OpenSource; + const isOnPrem = !config.pluginAdminExternalManageEnabled; - let cardsData = isOSS ? getOssCardData() : getCloudCardData(); + let cardsData = isOnPrem ? getOssCardData() : getCloudCardData(); return ( <Page @@ -31,7 +30,7 @@ export default function ConnectionsHomePage() { <Trans i18nKey="connections.connections-home-page.welcome-to-connections">Welcome to Connections</Trans> </h1> <p className={styles.subTitle}> - {isOSS ? ( + {isOnPrem ? ( <Trans i18nKey="connections.oss.connections-home-page.subtitle"> Manage your data source connections in one place. Use this page to add a new data source or manage your existing connections. From 3d112755dea6546d68e3cd14390241abf8b549f3 Mon Sep 17 00:00:00 2001 From: maicon <maiconscosta@gmail.com> Date: Tue, 14 Oct 2025 16:27:08 -0300 Subject: [PATCH 13/75] unistore: add timeout config for resource server joining the ring (#112392) Signed-off-by: Maicon Costa <maiconscosta@gmail.com> --- pkg/server/search_server_distributor_test.go | 5 +++++ pkg/setting/setting.go | 1 + pkg/setting/setting_unified_storage.go | 1 + pkg/storage/unified/sql/service.go | 2 +- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/server/search_server_distributor_test.go b/pkg/server/search_server_distributor_test.go index 520443bc15f..83c32fced37 100644 --- a/pkg/server/search_server_distributor_test.go +++ b/pkg/server/search_server_distributor_test.go @@ -314,6 +314,11 @@ func createStorageServerApi(t *testing.T, instanceId int, dbType, dbConnStr stri cfg.IndexPath = t.TempDir() + cfg.InstanceID cfg.IndexFileThreshold = testIndexFileThreshold cfg.Target = []string{modules.StorageServer} + // make sure the resource server has enough time to join the ring + // before the tests start sending traffic + // otherwise the tests will be flaky, + // also, tests are going to timeout after 300 seconds anyway + cfg.ResourceServerJoinRingTimeout = 300 * time.Second return initModuleServerForTest(t, cfg, Options{}, api.ServerOptions{}) } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index ed661529221..5cad5c1ba9b 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -601,6 +601,7 @@ type Cfg struct { SprinklesApiServerPageLimit int CACertPath string HttpsSkipVerify bool + ResourceServerJoinRingTimeout time.Duration // Secrets Management SecretsManagement SecretsManagerSettings diff --git a/pkg/setting/setting_unified_storage.go b/pkg/setting/setting_unified_storage.go index 759d076e51c..a3702dc0a3b 100644 --- a/pkg/setting/setting_unified_storage.go +++ b/pkg/setting/setting_unified_storage.go @@ -77,6 +77,7 @@ func (cfg *Cfg) setUnifiedStorageConfig() { cfg.SprinklesApiServerPageLimit = section.Key("sprinkles_api_server_page_limit").MustInt(10000) cfg.CACertPath = section.Key("ca_cert_path").String() cfg.HttpsSkipVerify = section.Key("https_skip_verify").MustBool(false) + cfg.ResourceServerJoinRingTimeout = section.Key("resource_server_join_ring_timeout").MustDuration(10 * time.Second) cfg.MaxFileIndexAge = section.Key("max_file_index_age").MustDuration(0) cfg.MinFileIndexBuildVersion = section.Key("min_file_index_build_version").MustString("") diff --git a/pkg/storage/unified/sql/service.go b/pkg/storage/unified/sql/service.go index 1e0c228cd1c..00e2ae73750 100644 --- a/pkg/storage/unified/sql/service.go +++ b/pkg/storage/unified/sql/service.go @@ -310,7 +310,7 @@ func (s *service) starting(ctx context.Context) error { if s.cfg.EnableSharding { s.log.Info("waiting until resource server is JOINING in the ring") - lfcCtx, cancel := context.WithTimeout(context.Background(), time.Second*10) + lfcCtx, cancel := context.WithTimeout(context.Background(), s.cfg.ResourceServerJoinRingTimeout) defer cancel() if err := ring.WaitInstanceState(lfcCtx, s.searchRing, s.ringLifecycler.GetInstanceID(), ring.JOINING); err != nil { return fmt.Errorf("error switching to JOINING in the ring: %s", err) From 01c4cb1fac911ba2b89fbcff2d68413709ed2c62 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:38:31 -0600 Subject: [PATCH 14/75] Dashboard Migrations: V4 - no-op and clean up dead code from DashboardMigrator.ts (#112225) --- .../pkg/migration/schemaversion/migrations.go | 3 +- .../pkg/migration/schemaversion/v4.go | 9 ++ .../pkg/migration/schemaversion/v4_test.go | 38 +++++++ .../migration/testdata/input/v4.no-op.json | 18 ++++ .../output/latest_version/v4.no-op.v42.json | 98 +++++++++++++++++++ .../output/single_version/v4.no-op.v4.json | 52 ++++++++++ eslint-suppressions.json | 2 +- .../dashboard/state/DashboardMigrator.ts | 16 +-- 8 files changed, 220 insertions(+), 16 deletions(-) create mode 100644 apps/dashboard/pkg/migration/schemaversion/v4.go create mode 100644 apps/dashboard/pkg/migration/schemaversion/v4_test.go create mode 100644 apps/dashboard/pkg/migration/testdata/input/v4.no-op.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/latest_version/v4.no-op.v42.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/single_version/v4.no-op.v4.json diff --git a/apps/dashboard/pkg/migration/schemaversion/migrations.go b/apps/dashboard/pkg/migration/schemaversion/migrations.go index 9242ff8356f..7fcf37ecab8 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migrations.go +++ b/apps/dashboard/pkg/migration/schemaversion/migrations.go @@ -7,7 +7,7 @@ import ( ) const ( - MIN_VERSION = 4 + MIN_VERSION = 3 LATEST_VERSION = 42 ) @@ -35,6 +35,7 @@ type PanelPluginInfo struct { func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionMigrationFunc { return map[int]SchemaVersionMigrationFunc{ + 4: V4, 5: V5, 6: V6, 7: V7, diff --git a/apps/dashboard/pkg/migration/schemaversion/v4.go b/apps/dashboard/pkg/migration/schemaversion/v4.go new file mode 100644 index 00000000000..02346ed8c25 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v4.go @@ -0,0 +1,9 @@ +package schemaversion + +import "context" + +// V4 is a no-op migration +func V4(_ context.Context, dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = 4 + return nil +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v4_test.go b/apps/dashboard/pkg/migration/schemaversion/v4_test.go new file mode 100644 index 00000000000..a4f9f856a72 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v4_test.go @@ -0,0 +1,38 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV4(t *testing.T) { + tests := []migrationTestCase{ + { + name: "v4 no-op migration, updates schema version only", + input: map[string]interface{}{ + "title": "V4 No-Op Migration Test Dashboard", + "schemaVersion": 3, + "panels": []interface{}{ + map[string]interface{}{ + "type": "graph", + "title": "Panel remains unchanged", + "id": 1, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V4 No-Op Migration Test Dashboard", + "schemaVersion": 4, + "panels": []interface{}{ + map[string]interface{}{ + "type": "graph", + "title": "Panel remains unchanged", + "id": 1, + }, + }, + }, + }, + } + runMigrationTests(t, tests, schemaversion.V4) +} diff --git a/apps/dashboard/pkg/migration/testdata/input/v4.no-op.json b/apps/dashboard/pkg/migration/testdata/input/v4.no-op.json new file mode 100644 index 00000000000..62ce65ca2a8 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v4.no-op.json @@ -0,0 +1,18 @@ +{ + "title": "V4 No-Op Migration Test", + "schemaVersion": 3, + "panels": [ + { + "id": 1, + "type": "singlestat" + }, + { + "id": 2, + "type": "singlestat" + }, + { + "id": 3, + "type": "graph" + } + ] +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v4.no-op.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v4.no-op.v42.json new file mode 100644 index 00000000000..8a7d37dc08e --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v4.no-op.v42.json @@ -0,0 +1,98 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "stat" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V4 No-Op Migration Test", + "weekStart": "" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v4.no-op.v4.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v4.no-op.v4.json new file mode 100644 index 00000000000..cc1caec781f --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v4.no-op.v4.json @@ -0,0 +1,52 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "singlestat", + "id": 1, + "type": "stat" + }, + { + "autoMigrateFrom": "singlestat", + "id": 2, + "type": "stat" + }, + { + "autoMigrateFrom": "graph", + "id": 3, + "type": "timeseries" + } + ], + "schemaVersion": 4, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V4 No-Op Migration Test", + "weekStart": "" +} \ No newline at end of file diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 829f5944cff..e17566e3b87 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2531,7 +2531,7 @@ "count": 2 }, "@typescript-eslint/no-explicit-any": { - "count": 18 + "count": 17 } }, "public/app/features/dashboard/state/DashboardModel.repeat.test.ts": { diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index 106b20aa135..0dec719d0d7 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -181,20 +181,8 @@ export class DashboardMigrator { // schema version 4 changes if (oldVersion < 4 && finalTargetVersion >= 4) { - // move aliasYAxis changes - panelUpgrades.push((panel: any) => { - if (panel.type !== 'graph') { - return panel; - } - - each(panel.aliasYAxis, (value, key) => { - panel.seriesOverrides = [{ alias: key, yaxis: value }]; - }); - - delete panel.aliasYAxis; - - return panel; - }); + // graph migration is handled through the auto migration + // see autoMigrateAngular map in public/app/features/dashboard/state/PanelModel.ts } if (oldVersion < 6 && finalTargetVersion >= 6) { From b90cb220ed60cc393c73797463bf9749f0efe518 Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck <eric.leijonmarck@gmail.com> Date: Tue, 14 Oct 2025 21:39:55 +0100 Subject: [PATCH 15/75] LBAC for data sources: Refactor for readablitity (#112371) * refactor to make it more readable * removes the field of restrictAccess --- pkg/api/datasources_test.go | 6 +++--- pkg/services/cleanup/cleanup.go | 2 +- pkg/services/datasources/models.go | 18 ++++++++++++------ pkg/services/datasources/models_test.go | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/pkg/api/datasources_test.go b/pkg/api/datasources_test.go index fa057d3ccd5..517dc6e9048 100644 --- a/pkg/api/datasources_test.go +++ b/pkg/api/datasources_test.go @@ -242,10 +242,10 @@ func TestAddDataSourceTeamHTTPHeaders(t *testing.T) { jsonData := simplejson.New() jsonData.Set("teamHttpHeaders", datasources.TeamHTTPHeaders{ Headers: datasources.TeamHeaders{ - tenantID: []datasources.TeamHTTPHeader{ + tenantID: []datasources.AccessRule{ { - Header: "Authorization", - Value: "foo!=bar", + Header: "Authorization", + LBACRule: "foo!=bar", }, }, }, diff --git a/pkg/services/cleanup/cleanup.go b/pkg/services/cleanup/cleanup.go index 628df57d8c9..44d8937579e 100644 --- a/pkg/services/cleanup/cleanup.go +++ b/pkg/services/cleanup/cleanup.go @@ -480,7 +480,7 @@ func (srv *CleanUpService) cleanupStaleLBACRules(ctx context.Context) { func (srv *CleanUpService) getLBACRulesForTeamsStillExisting(ctx context.Context, teamHeaders *datasources.TeamHTTPHeaders, orgID int64) (*datasources.TeamHTTPHeaders, int) { logger := srv.log.FromContext(ctx) - cleanedHeaders := &datasources.TeamHTTPHeaders{Headers: make(map[string][]datasources.TeamHTTPHeader)} + cleanedHeaders := &datasources.TeamHTTPHeaders{Headers: make(map[string][]datasources.AccessRule)} removedCount := 0 allTeams, err := srv.teamService.SearchTeams(ctx, &team.SearchTeamsQuery{ diff --git a/pkg/services/datasources/models.go b/pkg/services/datasources/models.go index 329d936dedb..61883fe37a5 100644 --- a/pkg/services/datasources/models.go +++ b/pkg/services/datasources/models.go @@ -89,15 +89,21 @@ type TeamHTTPHeadersJSONData struct { } type TeamHTTPHeaders struct { - Headers TeamHeaders `json:"headers"` - RestrictAccess bool `json:"restrictAccess"` + Headers TeamHeaders `json:"headers"` } -type TeamHeaders map[string][]TeamHTTPHeader +type TeamHeaders map[string][]AccessRule -type TeamHTTPHeader struct { +// a header is composed of a key:value. the key is the headername X-Prom-Label-Policy +// a header value is composed of a tenantID:rule. the tenantID is the tenantID of the tenant that the rule is for. +// the value is taken from https://grafana.com/docs/mimir/latest/manage/tools/mimirtool/#acl +// and each rule is +type AccessRule struct { Header string `json:"header"` - Value string `json:"value"` + // the LBACRule is the rule that is used to restrict access to the data source + // currently <tenantid>:<promqlrule> + // LBAC rule (e.g., "tenant:{ label=value }") + LBACRule string `json:"value"` } func GetTeamHTTPHeaders(jsonData *simplejson.Json) (*TeamHTTPHeaders, error) { @@ -126,7 +132,7 @@ func GetTeamHTTPHeaders(jsonData *simplejson.Json) (*TeamHTTPHeaders, error) { if header.Header == "" { return nil, errors.New("header name is missing or empty") } - if header.Value == "" { + if header.LBACRule == "" { return nil, errors.New("header value is missing or empty") } } diff --git a/pkg/services/datasources/models_test.go b/pkg/services/datasources/models_test.go index 5b992781da8..f6b77b82cf8 100644 --- a/pkg/services/datasources/models_test.go +++ b/pkg/services/datasources/models_test.go @@ -71,7 +71,7 @@ func TestTeamHTTPHeaders(t *testing.T) { want: &TeamHTTPHeaders{ Headers: TeamHeaders{ "101": { - {Header: "X-CUSTOM-HEADER", Value: "foo"}, + {Header: "X-CUSTOM-HEADER", LBACRule: "foo"}, }, }, }, From 3895849c857dbba869b04a593d4b6c6773eb837f Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 00:38:24 +0000 Subject: [PATCH 16/75] I18n: Download translations from Crowdin (#112399) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/de-DE/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/es-ES/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/fr-FR/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/hu-HU/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/id-ID/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/it-IT/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/ja-JP/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/ko-KR/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/nl-NL/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/pl-PL/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/pt-BR/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/pt-PT/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/ru-RU/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/sv-SE/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/tr-TR/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/zh-Hans/grafana.json | 33 +++++++++++++++++++++++++++-- public/locales/zh-Hant/grafana.json | 33 +++++++++++++++++++++++++++-- 18 files changed, 558 insertions(+), 36 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 6cc361bc462..eb4c859c056 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -1665,6 +1665,15 @@ "inspector-yaml-tab": { "apply": "Použít" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2330,6 +2339,9 @@ "rule-details-data-sources": { "label-data-source": "Zdroj dat" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Výraz" }, @@ -2584,6 +2596,10 @@ "alert-message": "Pravidlo výstrahy bylo přidáno nebo aktualizováno. Může trvat až minutu, než se změny projeví v zobrazení seznamu pravidel výstrah.", "alert-title": "Probíhá aktualizace" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Při vyhodnocování tohoto pravidla výstrahy se něco pokazilo" }, "rules": { @@ -2954,10 +2970,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Přidat výraz" @@ -4647,7 +4675,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "Nástěnka {{dashboardName}}" + "aria-label-dashboard-name": "Nástěnka {{dashboardName}}", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Zrušit načítání nástěnky" diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index e0453dcd48b..41824924b7f 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -1655,6 +1655,15 @@ "inspector-yaml-tab": { "apply": "Anwenden" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2314,6 +2323,9 @@ "rule-details-data-sources": { "label-data-source": "Datenquelle" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Ausdruck" }, @@ -2560,6 +2572,10 @@ "alert-message": "Die Warnregel wurde hinzugefügt oder aktualisiert. Es kann bis zu einer Minute dauern, bis Änderungen in der Listenansicht der Warnregeln angezeigt werden.", "alert-title": "Aktualisierung wird durchgeführt" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Bei der Bewertung dieser Warnregel ist ein Fehler aufgetreten" }, "rules": { @@ -2930,10 +2946,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Ausdruck hinzufügen" @@ -4607,7 +4635,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "{{dashboardName}} Dashboard" + "aria-label-dashboard-name": "{{dashboardName}} Dashboard", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Laden des Dashboards abbrechen" diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index f2eae8ac8d4..6fc2aa46aab 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -1655,6 +1655,15 @@ "inspector-yaml-tab": { "apply": "Aplicar" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2314,6 +2323,9 @@ "rule-details-data-sources": { "label-data-source": "Fuente de datos" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Expresión" }, @@ -2560,6 +2572,10 @@ "alert-message": "Se ha añadido o actualizado la regla de alerta. Los cambios pueden tardar hasta un minuto en aparecer en la vista de lista de reglas de alerta.", "alert-title": "Actualización en curso" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Se ha producido un error al evaluar esta regla de alerta" }, "rules": { @@ -2930,10 +2946,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Añadir expresión" @@ -4607,7 +4635,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "Dashboard {{dashboardName}}" + "aria-label-dashboard-name": "Dashboard {{dashboardName}}", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Cancelar la carga del dashboard" diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 03b4e75efc3..7887b3526d1 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -1655,6 +1655,15 @@ "inspector-yaml-tab": { "apply": "Appliquer" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2314,6 +2323,9 @@ "rule-details-data-sources": { "label-data-source": "Source de données" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Expression" }, @@ -2560,6 +2572,10 @@ "alert-message": "La règle d’alerte a été ajoutée ou mise à jour. Les modifications peuvent prendre jusqu’à une minute pour apparaître dans la vue de la liste des règles d’alerte.", "alert-title": "Mise à jour en cours" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Une erreur s’est produite lors de l’évaluation de cette règle d’alerte" }, "rules": { @@ -2930,10 +2946,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Ajouter une expression" @@ -4607,7 +4635,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "Tableau de bord {{dashboardName}}" + "aria-label-dashboard-name": "Tableau de bord {{dashboardName}}", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Annuler le chargement du tableau de bord" diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 9ae446e7312..9861ccc46c0 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -1655,6 +1655,15 @@ "inspector-yaml-tab": { "apply": "Alkalmaz" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2314,6 +2323,9 @@ "rule-details-data-sources": { "label-data-source": "Adatforrás" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Kifejezés" }, @@ -2560,6 +2572,10 @@ "alert-message": "Riasztási szabály lett hozzáadva vagy frissítve. Akár egy percig is eltarthat, amíg a módosítások megjelennek a Riasztási szabályok listanézetben.", "alert-title": "Frissítés folyamatban" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Valami hiba történt ennek a riasztási szabálynak az értékelése során" }, "rules": { @@ -2930,10 +2946,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Kifejezés hozzáadása" @@ -4607,7 +4635,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "{{dashboardName}} irányítópult" + "aria-label-dashboard-name": "{{dashboardName}} irányítópult", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Irányítópult betöltésének megszakítása" diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 015a0fed6f8..0b15556e001 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -1650,6 +1650,15 @@ "inspector-yaml-tab": { "apply": "Terapkan" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2306,6 +2315,9 @@ "rule-details-data-sources": { "label-data-source": "Sumber data" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Ekspresi" }, @@ -2548,6 +2560,10 @@ "alert-message": "Aturan peringatan telah ditambahkan atau diperbarui. Perubahan mungkin memerlukan waktu hingga satu menit untuk muncul di tampilan daftar aturan Peringatan.", "alert-title": "Pembaruan sedang berlangsung" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Terjadi kesalahan saat mengevaluasi aturan peringatan ini" }, "rules": { @@ -2918,10 +2934,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Tambah pola" @@ -4587,7 +4615,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "dasbor {{dashboardName}}" + "aria-label-dashboard-name": "dasbor {{dashboardName}}", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Batalkan memuat dasbor" diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 2894d291fd9..98f52e220f6 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -1655,6 +1655,15 @@ "inspector-yaml-tab": { "apply": "Applica" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2314,6 +2323,9 @@ "rule-details-data-sources": { "label-data-source": "Sorgente dati" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Espressione" }, @@ -2560,6 +2572,10 @@ "alert-message": "La regola di avviso è stata aggiunta o aggiornata. Potrebbe essere necessario attendere fino a un minuto per visualizzare le modifiche nella vista elenco delle regole di avviso.", "alert-title": "Aggiornamento in corso" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Si è verificato un errore durante la valutazione di questa regola di avviso" }, "rules": { @@ -2930,10 +2946,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Aggiungi espressione" @@ -4607,7 +4635,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "Dashboard {{dashboardName}}" + "aria-label-dashboard-name": "Dashboard {{dashboardName}}", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Annulla caricamento della dashboard" diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 18c2400f567..8b229b31c6c 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -1650,6 +1650,15 @@ "inspector-yaml-tab": { "apply": "適用" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2306,6 +2315,9 @@ "rule-details-data-sources": { "label-data-source": "データソース" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "式" }, @@ -2548,6 +2560,10 @@ "alert-message": "アラートルールが追加または更新されました。変更がアラートルール一覧に反映されるまでに、最大1分かかる場合があります。", "alert-title": "更新中" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "このアラートルールの評価中に問題が発生しました" }, "rules": { @@ -2918,10 +2934,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "式を追加" @@ -4587,7 +4615,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "{{dashboardName}}ダッシュボード" + "aria-label-dashboard-name": "{{dashboardName}}ダッシュボード", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "ダッシュボードの読み込みをキャンセル" diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 5cad8e1d46f..dbddbd32f19 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -1650,6 +1650,15 @@ "inspector-yaml-tab": { "apply": "적용" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2306,6 +2315,9 @@ "rule-details-data-sources": { "label-data-source": "데이터 소스" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "표현식" }, @@ -2548,6 +2560,10 @@ "alert-message": "경고 규칙이 추가되거나 업데이트되었습니다. 경고 규칙 목록 보기에 변경 사항이 표시되기까지 최대 1분이 소요될 수 있습니다.", "alert-title": "업데이트 진행 중" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "이 경고 규칙을 평가하는 중에 문제가 발생했습니다" }, "rules": { @@ -2918,10 +2934,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "표현식 추가" @@ -4587,7 +4615,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "{{dashboardName}} 대시보드" + "aria-label-dashboard-name": "{{dashboardName}} 대시보드", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "대시보드 로딩 취소" diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 9f3b0359973..db8524a5e52 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -1655,6 +1655,15 @@ "inspector-yaml-tab": { "apply": "Toepassen" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2314,6 +2323,9 @@ "rule-details-data-sources": { "label-data-source": "Gegevensbron" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Expressie" }, @@ -2560,6 +2572,10 @@ "alert-message": "Waarschuwingsregel is toegevoegd of bijgewerkt. Het kan tot een minuut duren voordat wijzigingen worden weergegeven in de lijstweergave met waarschuwingsregels.", "alert-title": "Update wordt uitgevoerd" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Er ging iets mis bij het evalueren van deze waarschuwingsregel" }, "rules": { @@ -2930,10 +2946,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Expressie toevoegen" @@ -4607,7 +4635,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "Dashboard {{dashboardName}}" + "aria-label-dashboard-name": "Dashboard {{dashboardName}}", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Laden van het dashboard annuleren" diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index f291bb51ae4..885c9b4b1e0 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -1665,6 +1665,15 @@ "inspector-yaml-tab": { "apply": "Zastosuj" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2330,6 +2339,9 @@ "rule-details-data-sources": { "label-data-source": "Źródło danych" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Wyrażenie" }, @@ -2584,6 +2596,10 @@ "alert-message": "Dodano lub zaktualizowano regułę alertu. Pojawienie się zmian w widoku Lista reguł alertów może potrwać do minuty.", "alert-title": "Aktualizacja w toku" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Podczas oceniania tej reguły alertu wystąpił problem" }, "rules": { @@ -2954,10 +2970,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Dodaj wyrażenie" @@ -4647,7 +4675,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "Pulpit {{dashboardName}}" + "aria-label-dashboard-name": "Pulpit {{dashboardName}}", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Anuluj wczytywanie pulpitu" diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 4f9d94c4d42..83ea25c2b1d 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -1655,6 +1655,15 @@ "inspector-yaml-tab": { "apply": "Aplicar" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2314,6 +2323,9 @@ "rule-details-data-sources": { "label-data-source": "Fonte de dados" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Expressão" }, @@ -2560,6 +2572,10 @@ "alert-message": "A regra de alerta foi adicionada ou atualizada. As alterações podem levar até um minuto para aparecer na visualização da lista de regras de alerta.", "alert-title": "Atualização em andamento" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Ocorreu um erro ao avaliar esta regra de alerta" }, "rules": { @@ -2930,10 +2946,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Adicionar expressão" @@ -4607,7 +4635,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "Painel {{dashboardName}}" + "aria-label-dashboard-name": "Painel {{dashboardName}}", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Cancelar carregamento do painel" diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 1be968974b2..c7841f5ac8d 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -1655,6 +1655,15 @@ "inspector-yaml-tab": { "apply": "Aplicar" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2314,6 +2323,9 @@ "rule-details-data-sources": { "label-data-source": "Origem dos dados" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Expressão" }, @@ -2560,6 +2572,10 @@ "alert-message": "A regra de alerta foi adicionada ou atualizada. As alterações podem demorar até um minuto a aparecer na vista de lista de regras de alerta.", "alert-title": "Atualização em progresso" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Ocorreu um erro ao avaliar esta regra de alerta" }, "rules": { @@ -2930,10 +2946,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Adicionar expressão" @@ -4607,7 +4635,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "Painel de controlo {{dashboardName}}" + "aria-label-dashboard-name": "Painel de controlo {{dashboardName}}", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Cancelar carregamento do painel de controlo" diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index e2613192cfe..817cd48e9fb 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -1665,6 +1665,15 @@ "inspector-yaml-tab": { "apply": "Применить" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2330,6 +2339,9 @@ "rule-details-data-sources": { "label-data-source": "Источник данных" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Выражение" }, @@ -2584,6 +2596,10 @@ "alert-message": "Добавлено или обновлено правило оповещения. Изменения могут отобразиться в списке правил оповещений в течение минуты.", "alert-title": "Выполняется обновление" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Ошибка при оценке правила оповещения" }, "rules": { @@ -2954,10 +2970,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Добавить выражение" @@ -4647,7 +4675,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "Дашборд {{dashboardName}}" + "aria-label-dashboard-name": "Дашборд {{dashboardName}}", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Отменить загрузку дашборда" diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 2b06c901d45..0a9891de27d 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -1655,6 +1655,15 @@ "inspector-yaml-tab": { "apply": "Tillämpa" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2314,6 +2323,9 @@ "rule-details-data-sources": { "label-data-source": "Datakälla" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "Uttryck" }, @@ -2560,6 +2572,10 @@ "alert-message": "Larmregel har lagts till eller uppdaterats. Det kan ta upp till en minut innan ändringar visas i listvyn för larmregler.", "alert-title": "Uppdatering pågår" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Något gick fel vid utvärdering av den här larmregeln" }, "rules": { @@ -2930,10 +2946,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "Lägg till uttryck" @@ -4607,7 +4635,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "{{dashboardName}} instrumentpanel" + "aria-label-dashboard-name": "{{dashboardName}} instrumentpanel", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Avbryt laddning av instrumentpanel" diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index daad33632a8..14e8e592605 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -1655,6 +1655,15 @@ "inspector-yaml-tab": { "apply": "Uygula" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2314,6 +2323,9 @@ "rule-details-data-sources": { "label-data-source": "Veri kaynağı" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "İfade" }, @@ -2560,6 +2572,10 @@ "alert-message": "Uyarı kuralı eklendi veya güncellendi. Değişikliklerin Uyarı kuralları listesinde görünmesi bir dakikayı bulabilir.", "alert-title": "Güncelleme sürüyor" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "Bu uyarı kuralı değerlendirilirken bir hata oluştu" }, "rules": { @@ -2930,10 +2946,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "İfade ekle" @@ -4607,7 +4635,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "{{dashboardName}} panosu" + "aria-label-dashboard-name": "{{dashboardName}} panosu", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "Pano yüklemesini iptal et" diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index c3cdc7f791a..a8aad548310 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -1650,6 +1650,15 @@ "inspector-yaml-tab": { "apply": "应用" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2306,6 +2315,9 @@ "rule-details-data-sources": { "label-data-source": "数据源" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "表达式" }, @@ -2548,6 +2560,10 @@ "alert-message": "警报规则已添加或更新。更改可能需要花费一分钟才会显示在“提醒规则”列表视图中。", "alert-title": "正在升级" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "评估此警报规则时出错" }, "rules": { @@ -2918,10 +2934,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "添加表达式" @@ -4587,7 +4615,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "{{dashboardName}} 数据面板" + "aria-label-dashboard-name": "{{dashboardName}} 数据面板", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "取消加载数据面板" diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index c0fd2781fcb..b35477c68bf 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -1650,6 +1650,15 @@ "inspector-yaml-tab": { "apply": "套用" }, + "instance-details": { + "history-error": "", + "history-error-desc": "", + "no-history": "", + "state-history": "" + }, + "instance-details-drawer": { + "title-instance-details": "" + }, "instance-match": { "non-matching-labels": "", "notification-policy": "" @@ -2306,6 +2315,9 @@ "rule-details-data-sources": { "label-data-source": "資料來源" }, + "rule-details-drawer": { + "go-to-detail-view": "" + }, "rule-details-expression": { "label-expression": "表達式" }, @@ -2548,6 +2560,10 @@ "alert-message": "警報規則已新增或更新。變更可能需要一分鐘才會顯示在「警報規則」清單檢視中。", "alert-title": "更新進行中" }, + "tab": { + "details": "", + "query-conditions": "" + }, "title-something-wrong-evaluating-alert": "評估此警報規則時發生錯誤" }, "rules": { @@ -2918,10 +2934,22 @@ }, "triage": { "alert-instances": "", + "error-loading-rule": "", "firing-instances-count": "", + "instance-details": "", "no-instances-found": "", "no-labels": "", - "pending-instances-count": "" + "open-in-sidebar": "", + "open-rule-details": "", + "pending-instances-count": "", + "rule-details": { + "subtitle": "", + "title": "" + }, + "rule-not-found": { + "description": "", + "title": "" + } }, "type-selector-button": { "add-expression": "新增表達式" @@ -4587,7 +4615,8 @@ } }, "dashboard-links-menu": { - "aria-label-dashboard-name": "「{{dashboardName}}」儀表板" + "aria-label-dashboard-name": "「{{dashboardName}}」儀表板", + "label-no-dashboards-found": "" }, "dashboard-loading": { "cancel-loading-dashboard": "取消載入儀表板" From e5ef29c202e93314eb8d298873c861d8fc421c8a Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Tue, 14 Oct 2025 18:39:24 -0600 Subject: [PATCH 17/75] Dashboard Migrations: V3 - no-op; remove v3 from DashboardMigrator.ts (#112239) --- .../pkg/migration/schemaversion/migrations.go | 3 +- .../pkg/migration/schemaversion/v16.go | 15 ++- .../pkg/migration/schemaversion/v3.go | 9 ++ .../pkg/migration/schemaversion/v3_test.go | 38 ++++++ .../migration/testdata/input/v3.no-op.json | 21 ++++ .../output/latest_version/v3.no-op.v42.json | 114 ++++++++++++++++++ .../output/single_version/v3.no-op.v3.json | 53 ++++++++ .../dashboard/state/DashboardMigrator.ts | 27 ++--- 8 files changed, 260 insertions(+), 20 deletions(-) create mode 100644 apps/dashboard/pkg/migration/schemaversion/v3.go create mode 100644 apps/dashboard/pkg/migration/schemaversion/v3_test.go create mode 100644 apps/dashboard/pkg/migration/testdata/input/v3.no-op.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/latest_version/v3.no-op.v42.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/single_version/v3.no-op.v3.json diff --git a/apps/dashboard/pkg/migration/schemaversion/migrations.go b/apps/dashboard/pkg/migration/schemaversion/migrations.go index 7fcf37ecab8..a577b7f2bba 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migrations.go +++ b/apps/dashboard/pkg/migration/schemaversion/migrations.go @@ -7,7 +7,7 @@ import ( ) const ( - MIN_VERSION = 3 + MIN_VERSION = 2 LATEST_VERSION = 42 ) @@ -35,6 +35,7 @@ type PanelPluginInfo struct { func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionMigrationFunc { return map[int]SchemaVersionMigrationFunc{ + 3: V3, 4: V4, 5: V5, 6: V6, diff --git a/apps/dashboard/pkg/migration/schemaversion/v16.go b/apps/dashboard/pkg/migration/schemaversion/v16.go index da6db1dcc1a..343f496f6dc 100644 --- a/apps/dashboard/pkg/migration/schemaversion/v16.go +++ b/apps/dashboard/pkg/migration/schemaversion/v16.go @@ -271,19 +271,30 @@ func (r *rowArea) getPanelPosition(panelHeight int, panelWidth int) map[string]i func getMaxPanelID(rows []interface{}) int { maxID := 0 + hasValidID := false + for _, rowInterface := range rows { if row, ok := rowInterface.(map[string]interface{}); ok { if panels, ok := row["panels"].([]interface{}); ok { for _, panelInterface := range panels { if panel, ok := panelInterface.(map[string]interface{}); ok { - if id := GetIntValue(panel, "id", 0); id > maxID { - maxID = id + if id := GetIntValue(panel, "id", 0); id > 0 { + hasValidID = true + if id > maxID { + maxID = id + } } } } } } } + + // If no valid IDs found, return 0 (matches frontend behavior) + if !hasValidID { + return 0 + } + return maxID } diff --git a/apps/dashboard/pkg/migration/schemaversion/v3.go b/apps/dashboard/pkg/migration/schemaversion/v3.go new file mode 100644 index 00000000000..54163d2b685 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v3.go @@ -0,0 +1,9 @@ +package schemaversion + +import "context" + +// V3 is a no-op migration +func V3(_ context.Context, dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = 3 + return nil +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v3_test.go b/apps/dashboard/pkg/migration/schemaversion/v3_test.go new file mode 100644 index 00000000000..cbd0b4e43ce --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v3_test.go @@ -0,0 +1,38 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV3(t *testing.T) { + tests := []migrationTestCase{ + { + name: "v3 no-op migration, updates schema version only", + input: map[string]interface{}{ + "title": "V3 No-Op Migration Test Dashboard", + "schemaVersion": 2, + "panels": []interface{}{ + map[string]interface{}{ + "type": "graph", + "title": "Panel remains unchanged", + "id": 1, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V3 No-Op Migration Test Dashboard", + "schemaVersion": 3, + "panels": []interface{}{ + map[string]interface{}{ + "type": "graph", + "title": "Panel remains unchanged", + "id": 1, + }, + }, + }, + }, + } + runMigrationTests(t, tests, schemaversion.V3) +} diff --git a/apps/dashboard/pkg/migration/testdata/input/v3.no-op.json b/apps/dashboard/pkg/migration/testdata/input/v3.no-op.json new file mode 100644 index 00000000000..4fd95fc5353 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v3.no-op.json @@ -0,0 +1,21 @@ +{ + "title": "V3 No-Op Migration - but tests ensuring panel IDs are unique", + "schemaVersion": 2, + "panels": [ + { + "id": 1, + "type": "timeseries" + }, + { + "id": 2, + "type": "timeseries" + }, + { + "id": 2, + "type": "barchart" + }, + { + "type": "barchart" + } + ] +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v3.no-op.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v3.no-op.v42.json new file mode 100644 index 00000000000..e4f06710eed --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v3.no-op.v42.json @@ -0,0 +1,114 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 1, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 2, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 3, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "barchart" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 4, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "type": "barchart" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V3 No-Op Migration - but tests ensuring panel IDs are unique", + "weekStart": "" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v3.no-op.v3.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v3.no-op.v3.json new file mode 100644 index 00000000000..14e9dc81daf --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v3.no-op.v3.json @@ -0,0 +1,53 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "id": 1, + "type": "timeseries" + }, + { + "id": 2, + "type": "timeseries" + }, + { + "id": 3, + "type": "barchart" + }, + { + "id": 4, + "type": "barchart" + } + ], + "schemaVersion": 3, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V3 No-Op Migration - but tests ensuring panel IDs are unique", + "weekStart": "" +} \ No newline at end of file diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index 0dec719d0d7..43b88a92eb0 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -167,16 +167,8 @@ export class DashboardMigrator { // schema version 3 changes if (oldVersion < 3 && finalTargetVersion >= 3) { - // ensure panel IDs - let maxId = this.dashboard.getNextPanelId(); - panelUpgrades.push((panel: PanelModel) => { - if (!panel.id) { - panel.id = maxId; - maxId += 1; - } - - return panel; - }); + // Panel ID assignment is now handled by ensurePanelsHaveUniqueIds() in DashboardModel + // and the grid layout migration properly handles panels without IDs in rows } // schema version 4 changes @@ -865,13 +857,14 @@ export class DashboardMigrator { let yPos = 0; const widthFactor = GRID_COLUMN_COUNT / 12; - const maxPanelId = max( - flattenDeep( - map(old.rows, (row) => { - return map(row.panels, 'id'); - }) - ) - ); + const maxPanelId = + max( + flattenDeep( + map(old.rows, (row) => { + return map(row.panels, 'id'); + }) + ).filter((id) => id != null) + ) || 0; let nextRowId = maxPanelId + 1; if (!old.rows) { From 2b4292fcfd9768f50634f76e8237e1ac4f89d2fb Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck <eric.leijonmarck@gmail.com> Date: Wed, 15 Oct 2025 08:19:32 +0100 Subject: [PATCH 18/75] LBAC for data sources: Cleanup service needs `serviceIdentity` to reach teams (#112378) service identity --- pkg/services/cleanup/cleanup.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/services/cleanup/cleanup.go b/pkg/services/cleanup/cleanup.go index 44d8937579e..54bf0d47bcf 100644 --- a/pkg/services/cleanup/cleanup.go +++ b/pkg/services/cleanup/cleanup.go @@ -454,8 +454,11 @@ func (srv *CleanUpService) cleanupStaleLBACRules(ctx context.Context) { totalDataSources++ + // needed for permissions to search teams and update data source + // msg="Failed to get teams for LBAC cleanup" error="missing permissions" + ctx, systemIdentity := identity.WithServiceIdentity(ctx, ds.OrgID) // Extract team UIDs and check if teams still exist - cleanedRules, removedCount := srv.getLBACRulesForTeamsStillExisting(ctx, teamHTTPHeaders, ds.OrgID) + cleanedRules, removedCount := srv.getLBACRulesForTeamsStillExisting(ctx, teamHTTPHeaders, ds.OrgID, systemIdentity) if removedCount > 0 { // Update the datasource with cleaned rules @@ -478,13 +481,15 @@ func (srv *CleanUpService) cleanupStaleLBACRules(ctx context.Context) { } } -func (srv *CleanUpService) getLBACRulesForTeamsStillExisting(ctx context.Context, teamHeaders *datasources.TeamHTTPHeaders, orgID int64) (*datasources.TeamHTTPHeaders, int) { +func (srv *CleanUpService) getLBACRulesForTeamsStillExisting(ctx context.Context, teamHeaders *datasources.TeamHTTPHeaders, orgID int64, systemIdentity identity.Requester) (*datasources.TeamHTTPHeaders, int) { logger := srv.log.FromContext(ctx) cleanedHeaders := &datasources.TeamHTTPHeaders{Headers: make(map[string][]datasources.AccessRule)} removedCount := 0 + // needed for permissions to search for teams allTeams, err := srv.teamService.SearchTeams(ctx, &team.SearchTeamsQuery{ - OrgID: orgID, + OrgID: orgID, + SignedInUser: systemIdentity, }) if err != nil { logger.Error("Failed to get teams for LBAC cleanup", "error", err) From 54c224dae78ef7aefe2f6c2889826528b08ec31e Mon Sep 17 00:00:00 2001 From: Mihai Doarna <mihai.doarna@grafana.com> Date: Wed, 15 Oct 2025 11:21:33 +0300 Subject: [PATCH 19/75] IAM: Add the external field to the TeamBinding spec (#112382) * add the external field to the TeamBinding spec * fix unit tests --- apps/iam/go.mod | 34 ++- apps/iam/go.sum | 248 +++++++++++++++++- apps/iam/kinds/v0alpha1/teambindingspec.cue | 2 + .../apis/iam/v0alpha1/teambinding_spec_gen.go | 1 + .../pkg/apis/iam/v0alpha1/zz_openapi_gen.go | 9 +- pkg/registry/apis/iam/legacy/team.go | 2 +- .../apis/iam/legacy/team_bindings_query.sql | 2 +- ...--team_bindings_query-team_bindings_id.sql | 2 +- ...am_bindings_query-team_bindings_page_1.sql | 2 +- ...am_bindings_query-team_bindings_page_2.sql | 2 +- ...--team_bindings_query-team_bindings_id.sql | 2 +- ...am_bindings_query-team_bindings_page_1.sql | 2 +- ...am_bindings_query-team_bindings_page_2.sql | 2 +- ...--team_bindings_query-team_bindings_id.sql | 2 +- ...am_bindings_query-team_bindings_page_1.sql | 2 +- ...am_bindings_query-team_bindings_page_2.sql | 2 +- pkg/registry/apis/iam/team/store_binding.go | 3 +- .../iam/team_bindings_integration_test.go | 2 + .../testdata/teambinding-test-create-v0.yaml | 1 + .../iam.grafana.app-v0alpha1.json | 14 +- 20 files changed, 303 insertions(+), 33 deletions(-) diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 7e77d0c830b..2514467aef1 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -12,6 +12,28 @@ replace github.com/grafana/grafana/apps/secret => ../secret replace github.com/grafana/grafana/apps/provisioning => ../provisioning +replace github.com/grafana/grafana/apps/advisor => ../advisor + +replace github.com/grafana/grafana/apps/alerting/alertenrichment => ../alerting/alertenrichment + +replace github.com/grafana/grafana/apps/alerting/notifications => ../alerting/notifications + +replace github.com/grafana/grafana/apps/alerting/rules => ../alerting/rules + +replace github.com/grafana/grafana/apps/correlations => ../correlations + +replace github.com/grafana/grafana/apps/investigations => ../investigations + +replace github.com/grafana/grafana/apps/playlist => ../playlist + +replace github.com/grafana/grafana/apps/plugins => ../plugins + +replace github.com/grafana/grafana/apps/preferences => ../preferences + +replace github.com/grafana/grafana/apps/scope => ../scope + +replace github.com/grafana/grafana/apps/shorturl => ../shorturl + replace github.com/grafana/grafana/pkg/apimachinery => ../../pkg/apimachinery replace github.com/grafana/grafana/pkg/apiserver => ../../pkg/apiserver @@ -45,14 +67,14 @@ require ( cuelang.org/go v0.11.1 // indirect dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect github.com/BurntSushi/toml v1.5.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect @@ -200,13 +222,13 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.14.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/alerting v0.0.0-20251007160934-e642236ea9eb // indirect + github.com/grafana/alerting v0.0.0-20251009192429-9427c24835ae // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect github.com/grafana/grafana-aws-sdk v1.2.0 // indirect - github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 // indirect + github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // indirect github.com/grafana/grafana-plugin-sdk-go v0.279.0 // indirect github.com/grafana/grafana/apps/dashboard v0.0.0 // indirect github.com/grafana/grafana/apps/provisioning v0.0.0 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index aa2d47b9a2e..3ea1d3638b5 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -1,3 +1,7 @@ +buf.build/gen/go/parca-dev/parca/connectrpc/go v1.18.1-20250703125925-3f0fcf4bff96.1 h1:MXQkdpJwbCwskYLQ3dUv/5gmk8fSsUwu78u+kXebbek= +buf.build/gen/go/parca-dev/parca/connectrpc/go v1.18.1-20250703125925-3f0fcf4bff96.1/go.mod h1:pjl83IqpNF7Lm/lOPMLQ5IIfCg+yTu2A9OgYTcEncCs= +buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.36.2-20250703125925-3f0fcf4bff96.1 h1:9nqE/pDc/HXAXiD5pZncPywjAzWgKuBkFFYgdK2lVU8= +buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.36.2-20250703125925-3f0fcf4bff96.1/go.mod h1:1M7nlq2ljfzb95x9LaA2j1gYIvDkVZii58mGvTa9ExM= cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -56,6 +60,8 @@ cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1 cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk= +cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= @@ -76,6 +82,8 @@ cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2zn cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= +connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= +connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 h1:R5wwEcbEZSBmeyg91MJZTxfd7WpBo2jPof3AYjRbxwY= cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg= cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0= @@ -83,37 +91,64 @@ cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o= +filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/1NCE-GmbH/grpc-go-pool v0.0.0-20231117122434-2a5bb974daa2 h1:qFYgLH2zZe3WHpQgUrzeazC+ebDebwAQqS9yE1cP5Bs= +github.com/1NCE-GmbH/grpc-go-pool v0.0.0-20231117122434-2a5bb974daa2/go.mod h1:09/ALd1AXCTCOfcJYD8+jIYKmFmi6PVCkTsipC18F7E= +github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= +github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 h1:wL5IEG5zb7BVv1Kv0Xm92orq+5hB5Nipn3B5tn4Rqfk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0 h1:LR0kAX9ykz8G4YgLCaRDVJ3+n43R8MneB5dTy2konZo= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0/go.mod h1:DWAciXemNf++PQJLeXUB4HHH5OpsAh12HZnu2wXE1jA= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 h1:lhZdRq7TIx0GJQvSyX2Si406vrYsov2FXGp/RnSEtcs= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1/go.mod h1:8cl44BDmi+effbARHMQjgOKA2AYvcohNm7KEt42mSV8= +github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= +github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= +github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= +github.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4= +github.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc= github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M= +github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= +github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 h1:XkkQbfMyuH2jTSjQjSoihryI8GINRcs4xp8lNawg0FI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/FZambia/eagle v0.2.0 h1:1kQaZpJvbkvAXFRE/9K2ucBMuVqo+E29EMLYB74hIis= +github.com/FZambia/eagle v0.2.0/go.mod h1:LKMYBwGYhao5sJI0TppvQ4SvvldFj9gITxrl8NvGwG0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= @@ -157,6 +192,8 @@ github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgX github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f/go.mod h1:f3HiCrHjHBdcm6E83vGaXh1KomZMA2P6aeo3hKx/wg0= github.com/Yiling-J/theine-go v0.6.2 h1:1GeoXeQ0O0AUkiwj2S9Jc0Mzx+hpqzmqsJ4kIC4M9AY= github.com/Yiling-J/theine-go v0.6.2/go.mod h1:08QpMa5JZ2pKN+UJCRrCasWYO1IKCdl54Xa836rpmDU= +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -175,6 +212,10 @@ github.com/apache/arrow-go/v18 v18.4.0/go.mod h1:Aawvwhj8x2jURIzD9Moy72cF0FyJXOp github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc= github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -212,6 +253,12 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.45.3 h1:Nn3qce+OHZuMj/edx4its32uxedAmquCDxtZkrdeiD4= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.45.3/go.mod h1:aqsLGsPs+rJfwDBwWHLcIV8F7AFcikFTPLwUD4RwORQ= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 h1:e5cbPZYTIY2nUEFieZUfVdINOiCTvChOMPfdLnmiLzs= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0/go.mod h1:UseIHRfrm7PqeZo6fcTb6FUCXzCnh1KJbQbmOfxArGM= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 h1:IfMb3Ar8xEaWjgH/zeVHYD8izwJdQgRP5mKCTDt4GNk= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2/go.mod h1:35jGWx7ECvCwTsApqicFYzZ7JFEnBc6oHUuOQ3xIS54= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU= @@ -220,6 +267,12 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0Na github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA= +github.com/aws/aws-sdk-go-v2/service/kms v1.38.1 h1:tecq7+mAav5byF+Mr+iONJnCBf4B4gon8RSp4BrweSc= +github.com/aws/aws-sdk-go-v2/service/kms v1.38.1/go.mod h1:cQn6tAF77Di6m4huxovNM7NVAozWTZLsDRp9t8Z/WYk= +github.com/aws/aws-sdk-go-v2/service/oam v1.18.3 h1:teOWtElLARLOhpYWwupjLbY9j5I/yZ/H1I8jg41An78= +github.com/aws/aws-sdk-go-v2/service/oam v1.18.3/go.mod h1:wGhpdyftHX6/1U4egowHkYdypwBMjpb+KjAAprv6z20= +github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6 h1:PwbxovpcJvb25k019bkibvJfCpCmIANOFrXZIFPmRzk= +github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6/go.mod h1:Z4xLt5mXspLKjBV92i165wAJ/3T6TIv4n7RtIS8pWV0= github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4= github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc= github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 h1:ve9dYBB8CfJGTFqcQ3ZLAAb/KXWgYlgu/2R2TZL2Ko0= @@ -239,6 +292,8 @@ github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df h1:GSoSVRLo github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df/go.mod h1:hiVxq5OP2bUGBRNS3Z/bt/reCLFNbdcST6gISi1fiOM= github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo4tARvBm7l6KA9iVMnE3NWizDeWSrps= github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0= +github.com/beevik/etree v1.4.1 h1:PmQJDDYahBGNKDcpdX8uPy1xRCwoCGVUiW669MEirVI= +github.com/beevik/etree v1.4.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -320,6 +375,8 @@ 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/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= +github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4= +github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= 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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -327,6 +384,11 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/centrifugal/centrifuge v0.37.2 h1:rerQNvDfYN2FZEkVtb/hvGV7SIrJfEQrKF3MaE8GDlo= +github.com/centrifugal/centrifuge v0.37.2/go.mod h1:aj4iRJGhzi3SlL8iUtVezxway1Xf8g+hmNQkLLO7sS8= +github.com/centrifugal/protocol v0.16.2 h1:KoIHgDeX1fFxyxQoKW+6E8ZTCf5mwGm8JyGoJ5NBMbQ= +github.com/centrifugal/protocol v0.16.2/go.mod h1:Q7OpS/8HMXDnL7f9DpNx24IhG96MP88WPpVTTCdrokI= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -378,6 +440,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c= +github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= @@ -431,6 +495,8 @@ github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e h1:7pAt github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e/go.mod h1:KZyoO3jngyZCLyCf100FEQTrwAHj33AIMj4Zv4u3MNE= github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= +github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ= +github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4= github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 h1:LGTt2LtYX8vaai32d+c9L0sMcP+Dg9w1kO6+lbsxxYg= github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -440,6 +506,8 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84= +github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= @@ -468,6 +536,8 @@ github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfU github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -489,6 +559,8 @@ github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6 github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= +github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= 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.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= @@ -547,6 +619,10 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+ github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +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-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= +github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= @@ -587,6 +663,10 @@ github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9v github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnbAaq2zgrPrs= github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= @@ -660,7 +740,10 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-github/v70 v70.0.0 h1:/tqCp5KPrcvqCc7vIvYyFYTiCGrYvaWoYMGHSQbo55o= +github.com/google/go-github/v70 v70.0.0/go.mod h1:xBUZgo8MI3lUL/hwxl3hlceJW1U8MVnXP3zUyI+rhQY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= @@ -723,8 +806,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20251007160934-e642236ea9eb h1:ejpL3pI9t1ebEFtxaP7TMDKZagCu1nSq1O8op+sO4DY= -github.com/grafana/alerting v0.0.0-20251007160934-e642236ea9eb/go.mod h1:VGjS5gDwWEADPP6pF/drqLxEImgeuHlEW5u8E5EfIrM= +github.com/grafana/alerting v0.0.0-20251009192429-9427c24835ae h1:NLPwY3tIP0lg0g9wTRiMcypm6VRXW6W+MOLBsq8JSVA= +github.com/grafana/alerting v0.0.0-20251009192429-9427c24835ae/go.mod h1:VGjS5gDwWEADPP6pF/drqLxEImgeuHlEW5u8E5EfIrM= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37 h1:qEwZ+7MbPjzRvTi31iT9w7NBhKIpKwZrFbYmOZLqkwA= @@ -735,30 +818,54 @@ github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6k github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= +github.com/grafana/e2e v0.1.1 h1:/b6xcv5BtoBnx8cZnCiey9DbjEc8z7gXHO5edoeRYxc= +github.com/grafana/e2e v0.1.1/go.mod h1:RpNLgae5VT+BUHvPE+/zSypmOXKwEu4t+tnEMS1ATaE= +github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f h1:5xkjl5Y/j2QefJKOtTfyD1wXlVsQ2yEXmd0u82h5obs= +github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f/go.mod h1:+O5QxOwwgP10jedZHapzXY+IPKTnzHBtIs5UUb9G+kI= +github.com/grafana/gomemcache v0.0.0-20250828162811-a96f6acee2fe h1:q+QaVANzNZxvTovycpQvDTfsNZ2rHh4XIIaccMnrIR4= +github.com/grafana/gomemcache v0.0.0-20250828162811-a96f6acee2fe/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= github.com/grafana/grafana-app-sdk v0.46.0 h1:gvzQvCQgZJ/73BfAcbDt/6TAMhnVikVPxZt/UwDl+oc= github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc= github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xuHKJjnO95/2pGOoA8w= github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-aws-sdk v1.2.0 h1:LLR4/g91WBuCRwm2cbWfCREq565+GxIFe08nqqIcIuw= github.com/grafana/grafana-aws-sdk v1.2.0/go.mod h1:bBo7qOmM3f61vO+2JxTolNUph1l2TmtzmWcU9/Im+8A= -github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA= -github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0/go.mod h1:H9sVh9A4yg5egMGZeh0mifxT1Q/uqwKe1LBjBJU6pN8= +github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk= +github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1/go.mod h1:Oi4anANlCuTCc66jCyqIzfVbgLXFll8Wja+Y4vfANlc= +github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 h1:JOzchPgptwJdruYoed7x28lFDwhzs7kssResYsnC0iI= +github.com/grafana/grafana-cloud-migration-snapshot v1.9.0/go.mod h1:nOHgq4Oa829qmBKA5KIXw5Ipo3rhLs0d6A8UI9Nw8Zk= +github.com/grafana/grafana-google-sdk-go v0.4.2 h1:F44hQF1y6UVJhlJPi+Mz+GCJsioVgezEgPMMEQbUZRo= +github.com/grafana/grafana-google-sdk-go v0.4.2/go.mod h1:U73+w9DlbEtUonhQUzERwlXnzWTtfRoyrtKH8d3VY40= +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.279.0 h1:/KCrsZkj9pEGwIGovqAz1A8rjI2A2YT+ZpvgfZN0LAA= github.com/grafana/grafana-plugin-sdk-go v0.279.0/go.mod h1:/7oGN6Z7DGTGaLHhgIYrRr6Wvmdsb3BLw5hL4Kbjy88= github.com/grafana/grafana/pkg/promlib v0.0.8 h1:VUWsqttdf0wMI4j9OX9oNrykguQpZcruudDAFpJJVw0= github.com/grafana/grafana/pkg/promlib v0.0.8/go.mod h1:U1ezG/MGaEPoThqsr3lymMPN5yIPdVTJnDZ+wcXT+ao= github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 h1:A65jWgLk4Re28gIuZcpC0aTh71JZ0ey89hKGE9h543s= github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:2HRzUK/xQEYc+8d5If/XSusMcaYq9IptnBSHACiQcOQ= +github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32 h1:NznuPwItog+rwdVg8hAuGKP29ndRSzJAwhxKldkP8oQ= +github.com/grafana/jsonparser v0.0.0-20240425183733-ea80629e1a32/go.mod h1:796sq+UcONnSlzA3RtlBZ+b/hrerkZXiEmO8oMjyRwY= +github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLmAhwA4m6iGUD4w1YkydEWWjazn9qxCFT8W0= +github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000/go.mod h1:/ZklAgE1i4f3Z8uriXwESmCr1VLF8lBGaJspuaGuf78= +github.com/grafana/loki/v3 v3.2.1 h1:VB7u+KHfvL5aHAxgoVBvz5wVhsdGuqKC7uuOFOOe7jw= +github.com/grafana/loki/v3 v3.2.1/go.mod h1:WvdLl6wOS+yahaeQY+xhD2m2XzkHDfKr5FZaX7D/X2Y= +github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 h1:cS0SlJGIlZbmDLctNj5vIYGemrJDLy25wwoiIyZWVN8= +github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 h1:aXfUhVN/Ewfpbko2CCtL65cIiGgwStOo4lWH2b6gw2U= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae h1:35W3Wjp9KWnSoV/DuymmyIj5aHE0CYlDQ5m2KeXUPAc= +github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae/go.mod h1:6CJ1uXmLZ13ufpO9xE4pST+DyaBt0uszzrV0YnoaVLQ= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grafana/sqlds/v4 v4.2.7 h1:sFQhsS7DBakNMdxa++yOfJ9BVvkZwFJ0B95o57K0/XA= github.com/grafana/sqlds/v4 v4.2.7/go.mod h1:BQRjUG8rOqrBI4NAaeoWrIMuoNgfi8bdhCJ+5cgEfLU= +github.com/grafana/tempo v1.5.1-0.20250529124718-87c2dc380cec h1:wnzJov9RhSHGaTYGzTygL4qq986fLen8xSqnQgaMd28= +github.com/grafana/tempo v1.5.1-0.20250529124718-87c2dc380cec/go.mod h1:j1IY7J2rUz7TcTjFVVx6HCpyTlYOJPtXuGRZ7sI+vSo= 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.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= @@ -808,6 +915,12 @@ github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVU github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= +github.com/hashicorp/go-secure-stdlib/plugincontainer v0.4.2 h1:gCNiM4T5xEc4IpT8vM50CIO+AtElr5kO9l2Rxbq+Sz8= +github.com/hashicorp/go-secure-stdlib/plugincontainer v0.4.2/go.mod h1:6ZM4ZdwClyAsiU2uDBmRHCvq0If/03BMbF9U+U7G5pA= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= @@ -825,7 +938,10 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= @@ -835,6 +951,8 @@ github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpT github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.10.2 h1:m5IORhuNSjaxeljg5DeQVDlQyVkhRIjJDimbkCa8aAc= github.com/hashicorp/serf v0.10.2/go.mod h1:T1CmSGfSeGfnfNy/w0odXQUR1rfECGd2Qdsp84DjOiY= +github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4= +github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -846,6 +964,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.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM= +github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4= +github.com/influxdata/influxql v1.4.0 h1:Lf62rbAF8KWQf+4Djqf4hVXgmQuGozUoSD6kNWjye44= +github.com/influxdata/influxql v1.4.0/go.mod h1:VqxAKyQz5p8GzgGsxWalCWYGxEqw6kvJo2IickMQiQk= +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.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= @@ -858,8 +982,22 @@ github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jaegertracing/jaeger v1.67.0 h1:t0BiJZVW9D3Z16y3uHqKzV9bKFTusooTH1Kgr77xF2Q= +github.com/jaegertracing/jaeger v1.67.0/go.mod h1:tE/FEQfybCSdUbBgel51YaCSkc58O+Njih8oTl6j8vw= github.com/jaegertracing/jaeger-idl v0.5.0 h1:zFXR5NL3Utu7MhPg8ZorxtCBjHrL3ReM1VoB65FOFGE= github.com/jaegertracing/jaeger-idl v0.5.0/go.mod h1:ON90zFo9eoyXrt9F/KN8YeF3zxcnujaisMweFY/rg5k= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= @@ -881,6 +1019,8 @@ github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbd github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 h1:hgVxRoDDPtQE68PT4LFvNlPz2nBKd3OMlGKIQ69OmR4= +github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531/go.mod h1:fqTUQpVYBvhCNIsMXGl2GE9q6z94DIP6NtFKXCSTVbg= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -945,6 +1085,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/m3db/prometheus_remote_client_golang v0.4.4 h1:DsAIjVKoCp7Ym35tAOFL1OuMLIdIikAEHeNPHY+yyM8= +github.com/m3db/prometheus_remote_client_golang v0.4.4/go.mod h1:wHfVbA3eAK6dQvKjCkHhusWYegCk3bDGkA15zymSHdc= github.com/madflojo/testcerts v1.1.1 h1:YsSHWV79nMNZK0mJtwXjKoYHjJEbLPFefR8TxmmWupY= github.com/madflojo/testcerts v1.1.1/go.mod h1:MW8sh39gLnkKh4K0Nc55AyHEDl9l/FBLDUsQhpmkuo0= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= @@ -955,6 +1097,8 @@ github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4 github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 h1:hQWBtNqRYrI7CWIaUSXXtNKR90KzcUA5uiuxFVWw7sU= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= +github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -965,6 +1109,8 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M= +github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= @@ -983,12 +1129,16 @@ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxU github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maypok86/otter v1.2.4 h1:HhW1Pq6VdJkmWwcZZq19BlEQkHtI8xgsQzBVXJU0nfc= +github.com/maypok86/otter v1.2.4/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/microsoft/go-mssqldb v1.9.2 h1:nY8TmFMQOHpm2qVWo6y4I2mAmVdZqlGiMGAYt64Ibbs= +github.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= @@ -1025,6 +1175,8 @@ github.com/mithrandie/ternary v1.1.1 h1:k/joD6UGVYxHixYmSR8EGgDFNONBMqyD373xT4QR github.com/mithrandie/ternary v1.1.1/go.mod h1:0D9Ba3+09K2TdSZO7/bFCC0GjSXetCvYuYq0u8FY/1g= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/mocktools/go-smtp-mock/v2 v2.3.1 h1:wq75NDSsOy5oHo/gEQQT0fRRaYKRqr1IdkjhIPXxagM= github.com/mocktools/go-smtp-mock/v2 v2.3.1/go.mod h1:h9AOf/IXLSU2m/1u4zsjtOM/WddPwdOUBz56dV9f81M= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1045,6 +1197,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= github.com/natefinch/wrap v0.2.0 h1:IXzc/pw5KqxJv55gV0lSOcKHYuEZPGbQrOOXr/bamRk= github.com/natefinch/wrap v0.2.0/go.mod h1:6gMHlAl12DwYEfKP3TkuykYUfLSEAvHw67itm4/KAS8= @@ -1053,6 +1207,8 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nikunjy/rules v1.5.0 h1:KJDSLOsFhwt7kcXUyZqwkgrQg5YoUwj+TVu6ItCQShw= github.com/nikunjy/rules v1.5.0/go.mod h1:TlZtZdBChrkqi8Lr2AXocme8Z7EsbxtFdDoKeI6neBQ= +github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo= +github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= @@ -1079,6 +1235,12 @@ github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3 h1:6jpO6 github.com/open-feature/go-sdk-contrib/providers/go-feature-flag v0.2.3/go.mod h1:dPUHjAIFzg+ci/wt6XxlNiiMkOh5Yw4SGyeRY0AFT0g= github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 h1:ZdqlGnNwhWf3luhBQlIpbglvcCzjkcuEgOEhYhr5Emc= github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5/go.mod h1:jrD4UG3ZCzuwImKHlyuIN2iWeYjlOX5+zJ/sX45efuE= +github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.124.1 h1:NrjsoVPxI6lmV8jPImDcMeqYh+97Y71f/HB5Sfpfe3I= +github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.124.1/go.mod h1:AFMryJmht7dZxcAwc2sx/r4gxbriElWw49ugxKp2mcA= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/core/xidutils v0.124.1 h1:E1e96GTHmiAfIfeYfA5ZVnOxud3+vbisGp0gE1tfd4s= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/core/xidutils v0.124.1/go.mod h1:MOhFATtYSLad9nKunjh6uGf8nQUcWje2LPlhD2uu3do= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.124.1 h1:9c6L4xlAMqhOg5y54Bc2B5t0i49yz7v2I6I8RY4Z0/o= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.124.1/go.mod h1:6f0N58o0cOHC0ApSM/qrooVmQza1eQ7L53PDE91uO1Q= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -1091,12 +1253,16 @@ github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250428093642-7aeebe78bbfe h github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20250428093642-7aeebe78bbfe/go.mod h1:5Z0pbTT7Jz/oQFLfadb+C5t5NwHrduAO7j7L07Ec1GM= github.com/openfga/openfga v1.10.0 h1:Ieq4fjJeT3KrxRiekgCnp9VCoXXnBvYITcTR35SPlYY= github.com/openfga/openfga v1.10.0/go.mod h1:6/m4GTwQsqECsGYQVD3t5sCX97rh3smnmxbMa3YAtJk= +github.com/opentracing-contrib/go-grpc v0.1.1 h1:Ws7IN1zyiL1DFqKQPhRXuKe5pLYzMfdxnC1qtajE2PE= +github.com/opentracing-contrib/go-grpc v0.1.1/go.mod h1:Nu6sz+4zzgxXu8rvKfnwjBEmHsuhTigxRwV2RhELrS8= github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= +github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -1110,9 +1276,13 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/phpdave11/gofpdi v1.0.14 h1:jlcDIJ6ObCh3X9nANGEK6RY5wbUKHJ5unBjrzG4i89A= +github.com/phpdave11/gofpdi v1.0.14/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= +github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1179,14 +1349,20 @@ github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= +github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0= +github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE= github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/redis/rueidis v1.0.64 h1:XqgbueDuNV3qFdVdQwAHJl1uNt90zUuAJuzqjH4cw6Y= +github.com/redis/rueidis v1.0.64/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y8MCluA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -1194,19 +1370,33 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys= +github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 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= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 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.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w= +github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= +github.com/sercand/kuberesolver/v6 v6.0.0 h1:ScvS2Ga9snVkpOahln/BCLySr3/iBAHJf25u66DweZ0= +github.com/sercand/kuberesolver/v6 v6.0.0/go.mod h1:Dxkqms3OJadP5zirIBPLi9FV8Qpys3T3w40XPEcVsu0= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3 h1:/4/IJi5iyTdh6mqOUaASW148HQpujYiHl0Wl78dSOSc= +github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3/go.mod h1:aJIMhRsunltJR926EB2MUg8qHemFQDreSB33pyto2Ps= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= @@ -1229,6 +1419,8 @@ github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sS github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= +github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -1256,6 +1448,8 @@ github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/spyzhov/ajson v0.9.6 h1:iJRDaLa+GjhCDAt1yFtU/LKMtLtsNVKkxqlpvrHHlpQ= +github.com/spyzhov/ajson v0.9.6/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1295,6 +1489,8 @@ github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJm 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/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90 h1:rB0J+hLNltG1Qv+UF+MkdFz89XMps5BOAFJN4xWjc+s= +github.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90/go.mod h1:BUbeWZiieNxAuuADTBNb3/aeje6on3DhU3rpWsQSB1E= 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= @@ -1312,6 +1508,12 @@ github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a/go.mod h1:1xEUf2abjfP9 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= +github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= +github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= +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/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8= +github.com/wk8/go-ordered-map v1.0.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk= 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/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= @@ -1323,12 +1525,24 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhe github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk= +github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= 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/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= @@ -1369,6 +1583,10 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/collector/pdata v1.30.0 h1:j3jyq9um436r6WzWySzexP2nLnFdmL5uVBYAlyr9nDM= +go.opentelemetry.io/collector/pdata v1.30.0/go.mod h1:0Bxu1ktuj4wE7PIASNSvd0SdBscQ1PLtYasymJ13/Cs= +go.opentelemetry.io/collector/semconv v0.124.0 h1:YTdo3UFwNyDQCh9DiSm2rbzAgBuwn/9dNZ0rv454goA= +go.opentelemetry.io/collector/semconv v0.124.0/go.mod h1:te6VQ4zZJO5Lp8dM2XIhDxDiL45mwX0YAQQWRQ0Qr9U= go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 h1:RyrtJzu5MAmIcbRrwg75b+w3RlZCP0vJByDVzcpAe3M= go.opentelemetry.io/contrib/bridges/prometheus v0.61.0/go.mod h1:tirr4p9NXbzjlbruiRGp53IzlYrDk5CO2fdHj0sSSaY= go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= @@ -1451,8 +1669,12 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go4.org/netipx v0.0.0-20230125063823-8449b0a6169f h1:ketMxHg+vWm3yccyYiq+uK8D3fRmna2Fcj+awpQp84s= +go4.org/netipx v0.0.0-20230125063823-8449b0a6169f/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc= gocloud.dev v0.42.0 h1:qzG+9ItUL3RPB62/Amugws28n+4vGZXEoJEAMfjutzw= gocloud.dev v0.42.0/go.mod h1:zkaYAapZfQisXOA4bzhsbA4ckiStGQ3Psvs9/OQ5dPM= +gocloud.dev/secrets/hashivault v0.42.0 h1:kPWIIu1AP6ApHf7HwUrzGrwyRJEfTd46JNUNgnbpsFA= +gocloud.dev/secrets/hashivault v0.42.0/go.mod h1:LXprr1XLEAT7BVZ+Y66dJEHQMzDsowIExj5Ktr9HLvM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -2061,6 +2283,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kms v0.34.1 h1:iCFOvewDPzWM9fMTfyIPO+4MeuZ0tcZbugxLNSHFG4w= k8s.io/kms v0.34.1/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= +k8s.io/kube-aggregator v0.34.1 h1:WNLV0dVNoFKmuyvdWLd92iDSyD/TSTjqwaPj0U9XAEU= +k8s.io/kube-aggregator v0.34.1/go.mod h1:RU8j+5ERfp0h+gIvWtxRPfsa5nK7rboDm8RST8BJfYQ= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= diff --git a/apps/iam/kinds/v0alpha1/teambindingspec.cue b/apps/iam/kinds/v0alpha1/teambindingspec.cue index c20e592e458..0e16650d639 100644 --- a/apps/iam/kinds/v0alpha1/teambindingspec.cue +++ b/apps/iam/kinds/v0alpha1/teambindingspec.cue @@ -11,6 +11,8 @@ TeamBindingSpec: { // permission of the identity in the team permission: TeamPermission + + external: bool } TeamRef:{ diff --git a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go index 13abd605168..87c976f7957 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go @@ -38,6 +38,7 @@ type TeamBindingSpec struct { TeamRef TeamBindingTeamRef `json:"teamRef"` // permission of the identity in the team Permission TeamBindingTeamPermission `json:"permission"` + External bool `json:"external"` } // NewTeamBindingSpec creates a new TeamBindingSpec object. diff --git a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go index 2532b5df8cc..b0489baabb3 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go @@ -2123,8 +2123,15 @@ func schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref common.ReferenceCallback) Format: "", }, }, + "external": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, }, - Required: []string{"subject", "teamRef", "permission"}, + Required: []string{"subject", "teamRef", "permission", "external"}, }, }, Dependencies: []string{ diff --git a/pkg/registry/apis/iam/legacy/team.go b/pkg/registry/apis/iam/legacy/team.go index a94bf44ad0a..191cc02cbee 100644 --- a/pkg/registry/apis/iam/legacy/team.go +++ b/pkg/registry/apis/iam/legacy/team.go @@ -509,7 +509,7 @@ func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.Namespa for rows.Next() { m := TeamMember{} - err = rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Created, &m.Updated, &m.Permission) + err = rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Created, &m.Updated, &m.Permission, &m.External) if err != nil { return res, err } diff --git a/pkg/registry/apis/iam/legacy/team_bindings_query.sql b/pkg/registry/apis/iam/legacy/team_bindings_query.sql index df8e8118ad5..668a66cf30e 100644 --- a/pkg/registry/apis/iam/legacy/team_bindings_query.sql +++ b/pkg/registry/apis/iam/legacy/team_bindings_query.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission, tm.external FROM {{ .Ident .TeamMemberTable }} tm INNER JOIN {{ .Ident .TeamTable }} t ON tm.team_id = t.id INNER JOIN {{ .Ident .UserTable }} u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_id.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_id.sql index 2ecf1da6529..c687c0a2d4b 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_id.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_id.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission, tm.external FROM `grafana`.`team_member` tm INNER JOIN `grafana`.`team` t ON tm.team_id = t.id INNER JOIN `grafana`.`user` u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_1.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_1.sql index b2eaa3a9f42..d0b562afec3 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_1.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_1.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission, tm.external FROM `grafana`.`team_member` tm INNER JOIN `grafana`.`team` t ON tm.team_id = t.id INNER JOIN `grafana`.`user` u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_2.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_2.sql index df9d37bcb2c..35855eefcd1 100755 --- a/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_2.sql +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--team_bindings_query-team_bindings_page_2.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission, tm.external FROM `grafana`.`team_member` tm INNER JOIN `grafana`.`team` t ON tm.team_id = t.id INNER JOIN `grafana`.`user` u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_id.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_id.sql index dbabf733606..5ca313393be 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_id.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_id.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission, tm.external FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_1.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_1.sql index 2abca6ed298..930103b36a9 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_1.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_1.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission, tm.external FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_2.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_2.sql index 0a93c590a11..63db6873ba7 100755 --- a/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_2.sql +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--team_bindings_query-team_bindings_page_2.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission, tm.external FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_id.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_id.sql index dbabf733606..5ca313393be 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_id.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_id.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission, tm.external FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_1.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_1.sql index 2abca6ed298..930103b36a9 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_1.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_1.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission, tm.external FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_2.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_2.sql index 0a93c590a11..63db6873ba7 100755 --- a/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_2.sql +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--team_bindings_query-team_bindings_page_2.sql @@ -1,4 +1,4 @@ -SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission +SELECT tm.id as id, t.uid as team_uid, t.id as team_id, u.uid as user_uid, u.id as user_id, tm.created, tm.updated, tm.permission, tm.external FROM "grafana"."team_member" tm INNER JOIN "grafana"."team" t ON tm.team_id = t.id INNER JOIN "grafana"."user" u ON tm.user_id = u.id diff --git a/pkg/registry/apis/iam/team/store_binding.go b/pkg/registry/apis/iam/team/store_binding.go index 0385b1fa3b3..35f28692ca9 100644 --- a/pkg/registry/apis/iam/team/store_binding.go +++ b/pkg/registry/apis/iam/team/store_binding.go @@ -136,7 +136,7 @@ func (l *LegacyBindingStore) Create(ctx context.Context, obj runtime.Object, cre UserID: userObj.ID, UserUID: teamMemberObj.Spec.Subject.Name, Permission: permission, - External: false, + External: teamMemberObj.Spec.External, } result, err := l.store.CreateTeamMember(ctx, ns, createCmd) @@ -229,6 +229,7 @@ func mapToBindingObject(ns claims.NamespaceInfo, tm legacy.TeamMember) iamv0alph Name: tm.UserUID, }, Permission: common.MapTeamPermission(tm.Permission), + External: tm.External, }, } } diff --git a/pkg/tests/apis/iam/team_bindings_integration_test.go b/pkg/tests/apis/iam/team_bindings_integration_test.go index 606cc629faf..dd4ba4b5de8 100644 --- a/pkg/tests/apis/iam/team_bindings_integration_test.go +++ b/pkg/tests/apis/iam/team_bindings_integration_test.go @@ -95,6 +95,7 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel require.Equal(t, user.GetName(), createdSpec["subject"].(map[string]interface{})["name"]) require.Equal(t, team.GetName(), createdSpec["teamRef"].(map[string]interface{})["name"]) require.Equal(t, "admin", createdSpec["permission"]) + require.Equal(t, false, createdSpec["external"]) createdUID := created.GetName() require.NotEmpty(t, createdUID) @@ -108,6 +109,7 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel require.Equal(t, user.GetName(), fetchedSpec["subject"].(map[string]interface{})["name"]) require.Equal(t, team.GetName(), fetchedSpec["teamRef"].(map[string]interface{})["name"]) require.Equal(t, "admin", fetchedSpec["permission"]) + require.Equal(t, false, fetchedSpec["external"]) require.Equal(t, createdUID, fetched.GetName()) require.Equal(t, "default", fetched.GetNamespace()) diff --git a/pkg/tests/apis/iam/testdata/teambinding-test-create-v0.yaml b/pkg/tests/apis/iam/testdata/teambinding-test-create-v0.yaml index 9baf0893499..da04e7785b1 100644 --- a/pkg/tests/apis/iam/testdata/teambinding-test-create-v0.yaml +++ b/pkg/tests/apis/iam/testdata/teambinding-test-create-v0.yaml @@ -8,3 +8,4 @@ spec: teamRef: name: "" permission: "admin" + external: false diff --git a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json index 856062877c3..bd0bee012c0 100644 --- a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json @@ -4343,9 +4343,14 @@ "required": [ "subject", "teamRef", - "permission" + "permission", + "external" ], "properties": { + "external": { + "type": "boolean", + "default": false + }, "permission": { "description": "permission of the identity in the team", "type": "string", @@ -6197,9 +6202,14 @@ "required": [ "subject", "teamRef", - "permission" + "permission", + "external" ], "properties": { + "external": { + "type": "boolean", + "default": false + }, "permission": { "description": "permission of the identity in the team", "type": "string", From aa89bcf3705dc641f9b6a82628ac68150a3fe78a Mon Sep 17 00:00:00 2001 From: Alexander Zobnin <alexanderzobnin@gmail.com> Date: Wed, 15 Oct 2025 10:37:23 +0200 Subject: [PATCH 20/75] grafana-iam: RoleBindings implementation (#112120) * add permissions for rolebindings * fix required actions * fix VerbCreate * transform to wildcard scope * Apply suggestions from code review Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> * Apply suggestion from @gamab * lint --------- Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> --- pkg/services/authz/rbac/mapper.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/services/authz/rbac/mapper.go b/pkg/services/authz/rbac/mapper.go index cf51380b22d..cdbe021b315 100644 --- a/pkg/services/authz/rbac/mapper.go +++ b/pkg/services/authz/rbac/mapper.go @@ -35,6 +35,8 @@ type translation struct { actionSetMapping map[string][]string folderSupport bool skipScopeOnCreate bool + // use this option if you need to limit access to users that can access all resources + useWildcardScope bool } func (t translation) Action(verb string) (string, bool) { @@ -48,6 +50,9 @@ func (t translation) ActionSets(verb string) []string { } func (t translation) Scope(name string) string { + if t.useWildcardScope { + return "*" + } return t.resource + ":" + t.attribute + ":" + name } @@ -198,6 +203,22 @@ func NewMapperRegistry() MapperRegistry { folderSupport: false, skipScopeOnCreate: false, }, + "rolebindings": translation{ + resource: "rolebindings", + // rolebidings should only be modifiable by admins with a wildcard access + useWildcardScope: true, + verbMapping: map[string]string{ + utils.VerbCreate: "users.roles:add", + utils.VerbGet: "users.roles:read", + utils.VerbUpdate: "users.roles:add", + utils.VerbPatch: "users.roles:add", + utils.VerbDelete: "users.roles:remove", + utils.VerbDeleteCollection: "users.roles:remove", + utils.VerbList: "users.roles:read", + utils.VerbWatch: "users.roles:read", + }, + folderSupport: false, + }, }, "secret.grafana.app": { "securevalues": newResourceTranslation("secret.securevalues", "uid", false, false), From bf9f6c255b7246bc641222ee5fa3f42054d4a065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?= <hugo.haggmark@gmail.com> Date: Wed, 15 Oct 2025 10:55:23 +0200 Subject: [PATCH 21/75] Docs: Update plugin.schema.json for clarity and completeness (#112402) * Update plugin.schema.json for clarity and completeness * chore: update descriptions for title and description fields --- .../developers/plugins/plugin.schema.json | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/docs/sources/developers/plugins/plugin.schema.json b/docs/sources/developers/plugins/plugin.schema.json index 8cb3954f13d..bc34bdad71d 100644 --- a/docs/sources/developers/plugins/plugin.schema.json +++ b/docs/sources/developers/plugins/plugin.schema.json @@ -106,6 +106,11 @@ "type": "string", "format": "uri", "description": "URL value to use for this specific link." + }, + "target": { + "type": "string", + "description": "A string that indicates where to display the linked resource", + "enum": ["_blank", "_self", "_parent", "_top"] } } } @@ -499,8 +504,8 @@ }, "state": { "type": "string", - "description": "Marks a plugin as a pre-release.", - "enum": ["alpha", "beta"] + "description": "Describes plugins life cycle status", + "enum": ["alpha", "beta", "stable", "deprecated"] }, "streaming": { "type": "boolean", @@ -588,7 +593,7 @@ "properties": { "addedComponents": { "type": "array", - "description": "This list must contain all component extensions that your plugin registers to other extension points using [`.addComponent()`](https://grafana.com/developers/plugin-tools/reference/ui-extensions#addcomponent). **Components that are not listed here won't work.**", + "description": "This list must contain all component extensions that your plugin registers to other extension points using [`.addComponent()`](https://grafana.com/developers/plugin-tools/reference/ui-extensions-reference/ui-extensions#addcomponent). **Components that are not listed here won't work.**", "items": { "type": "object", "properties": { @@ -614,7 +619,7 @@ }, "addedLinks": { "type": "array", - "description": "This list must contain all link extensions that your plugin registers to other extension points using [`.addLink()`](https://grafana.com/developers/plugin-tools/reference/ui-extensions#addlink). **Links that are not listed here won't work.**", + "description": "This list must contain all link extensions that your plugin registers to other extension points using [`.addLink()`](https://grafana.com/developers/plugin-tools/reference/ui-extensions-reference/ui-extensions#addlink). **Links that are not listed here won't work.**", "items": { "type": "object", "properties": { @@ -638,9 +643,35 @@ "required": ["targets", "title"] } }, + "addedFunctions": { + "type": "array", + "description": "This list must contain all function extensions that your plugin registers to other extension points using [`.addedFunctions()`](https://grafana.com/developers/plugin-tools/reference/ui-extensions-reference/ui-extensions#addfunction). **Functions that are not listed here won't work.**", + "items": { + "type": "object", + "properties": { + "targets": { + "type": "array", + "description": "The extension point ids your plugin registers the extension to, e.g. `[\"grafana/dashboard/panel/menu\"]`", + "items": { + "type": "string" + } + }, + "title": { + "type": "string", + "description": "The title of your function extension.", + "minLength": 10 + }, + "description": { + "type": "string", + "description": "Additional information about your function extension." + } + }, + "required": ["targets", "title"] + } + }, "exposedComponents": { "type": "array", - "description": "This list must contain all components that your plugin exposes using [`.exposeComponent()`](https://grafana.com/developers/plugin-tools/reference/ui-extensions#exposecomponent). **Components that are not listed here won't work.**", + "description": "This list must contain all components that your plugin exposes using [`.exposeComponent()`](https://grafana.com/developers/plugin-tools/reference/ui-extensions-reference/ui-extensions#exposecomponent). **Components that are not listed here won't work.**", "items": { "type": "object", "properties": { @@ -663,7 +694,7 @@ }, "extensionPoints": { "type": "array", - "description": "This list must contain all extension points that your plugin defines using [`usePluginLinks()`](https://grafana.com/developers/plugin-tools/reference/ui-extensions#usepluginlinks) or [`usePluginComponents()`](https://grafana.com/developers/plugin-tools/reference/ui-extensions#useplugincomponents). **Extension points that are not listed in here won't work.**", + "description": "This list must contain all extension points that your plugin defines using [`usePluginLinks()`](https://grafana.com/developers/plugin-tools/reference/ui-extensions-reference/ui-extensions#usepluginlinks) or [`usePluginComponents()`](https://grafana.com/developers/plugin-tools/reference/ui-extensions-reference/ui-extensions#useplugincomponents). **Extension points that are not listed in here won't work.**", "items": { "type": "object", "properties": { From c9af4ebb0968b3e1c45b9b3af5b1ac82fee102df Mon Sep 17 00:00:00 2001 From: Ashley Harrison <ashley.harrison@grafana.com> Date: Wed, 15 Oct 2025 10:58:21 +0100 Subject: [PATCH 22/75] Chore: Add lint rule preferring functional components, suppress existing violations (#112377) add lint rule preferring functional components, suppress existing violations --- eslint-suppressions.json | 880 ++++++++++++++++++ eslint.config.js | 3 + package.json | 1 + .../services/pluginExtensions/utils.test.tsx | 4 + yarn.lock | 8 + 5 files changed, 896 insertions(+) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index e17566e3b87..237e55d979c 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -470,6 +470,11 @@ "count": 1 } }, + "packages/grafana-runtime/src/components/DataSourcePicker.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-runtime/src/config.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 5 @@ -539,6 +544,11 @@ "count": 2 } }, + "packages/grafana-sql/src/components/ErrorBoundary.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-sql/src/components/configuration/ConnectionLimits.tsx": { "no-restricted-syntax": { "count": 2 @@ -564,9 +574,37 @@ "count": 1 } }, + "packages/grafana-ui/src/components/BarGauge/BarGauge.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/BigValue/BigValue.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/BigValue/BigValueLayout.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/Cascader/Cascader.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "packages/grafana-ui/src/components/Combobox/Combobox.story.tsx": { @@ -613,6 +651,9 @@ }, "no-restricted-syntax": { "count": 3 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "packages/grafana-ui/src/components/DataSourceSettings/SecureSocksProxySettings.tsx": { @@ -635,6 +676,11 @@ "count": 3 } }, + "packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-ui/src/components/FileDropzone/FileDropzone.story.tsx": { "no-restricted-syntax": { "count": 1 @@ -674,6 +720,9 @@ }, "no-restricted-syntax": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "packages/grafana-ui/src/components/Forms/Legacy/Select/NoOptionsMessage.tsx": { @@ -687,6 +736,9 @@ }, "no-restricted-syntax": { "count": 6 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 3 } }, "packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOption.tsx": { @@ -700,11 +752,22 @@ "packages/grafana-ui/src/components/Forms/Legacy/Select/SelectOptionGroup.tsx": { "@typescript-eslint/no-explicit-any": { "count": 3 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "packages/grafana-ui/src/components/Forms/Legacy/Switch/Switch.tsx": { "no-restricted-syntax": { "count": 3 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/Gauge/Gauge.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "packages/grafana-ui/src/components/Icon/Icon.story.tsx": { @@ -717,6 +780,11 @@ "count": 3 } }, + "packages/grafana-ui/src/components/JSONFormatter/JSONFormatter.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-ui/src/components/JSONFormatter/json_explorer/json_explorer.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 @@ -727,6 +795,21 @@ "count": 1 } }, + "packages/grafana-ui/src/components/List/AbstractList.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/List/InlineList.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/List/List.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-ui/src/components/MatchersUI/FieldValueMatcher.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -750,6 +833,14 @@ "packages/grafana-ui/src/components/Modal/ModalsContext.tsx": { "@typescript-eslint/no-explicit-any": { "count": 4 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/Monaco/CodeEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "packages/grafana-ui/src/components/PageLayout/PageToolbar.story.tsx": { @@ -777,6 +868,16 @@ "count": 1 } }, + "packages/grafana-ui/src/components/QueryField/QueryField.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx": { "no-restricted-syntax": { "count": 2 @@ -815,6 +916,11 @@ "count": 4 } }, + "packages/grafana-ui/src/components/Select/ValueContainer.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-ui/src/components/Select/resetSelectStyles.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 @@ -825,6 +931,11 @@ "count": 6 } }, + "packages/grafana-ui/src/components/SetInterval/SetInterval.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-ui/src/components/SingleStatShared/SingleStatBaseOptions.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 3 @@ -833,6 +944,21 @@ "count": 13 } }, + "packages/grafana-ui/src/components/Sparkline/Sparkline.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-ui/src/components/Table/Cells/TableCell.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 3 @@ -913,11 +1039,31 @@ "count": 1 } }, + "packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-ui/src/components/Tags/Tag.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 } }, + "packages/grafana-ui/src/components/Tooltip/PopoverController.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "packages/grafana-ui/src/components/Typeahead/Typeahead.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 2 + } + }, + "packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx": { "@grafana/no-aria-label-selectors": { "count": 1 @@ -936,6 +1082,9 @@ "packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "packages/grafana-ui/src/components/VizTooltip/VizTooltip.tsx": { @@ -951,6 +1100,9 @@ "packages/grafana-ui/src/components/uPlot/Plot.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "packages/grafana-ui/src/components/uPlot/config/UPlotAxisBuilder.ts": { @@ -1062,6 +1214,11 @@ "count": 1 } }, + "public/app/AppWrapper.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/core/TableModel.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 @@ -1113,6 +1270,14 @@ }, "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/core/components/Login/LoginCtrl.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/core/components/Login/LoginForm.tsx": { @@ -1138,6 +1303,9 @@ "public/app/core/components/OptionsUI/NumberInput.tsx": { "no-restricted-syntax": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/core/components/OptionsUI/fieldColor.tsx": { @@ -1145,6 +1313,21 @@ "count": 1 } }, + "public/app/core/components/OptionsUI/multiSelect.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/core/components/OptionsUI/select.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/core/components/OptionsUI/strings.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/core/components/Page/EditableTitle.tsx": { "no-restricted-syntax": { "count": 1 @@ -1165,10 +1348,28 @@ "count": 1 } }, + "public/app/core/components/Select/ServiceAccountPicker.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/core/components/Select/TeamPicker.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/core/components/Select/UserPicker.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/core/components/SharedPreferences/SharedPreferences.tsx": { "no-restricted-syntax": { "count": 8 }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + }, "react/no-unescaped-entities": { "count": 1 } @@ -1183,6 +1384,11 @@ "count": 1 } }, + "public/app/core/components/SplitPaneWrapper/SplitPaneWrapper.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/core/components/TagFilter/TagFilter.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -1193,6 +1399,11 @@ "count": 1 } }, + "public/app/core/components/TimeSeries/TimeSeries.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/core/components/TimeSeries/utils.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -1201,6 +1412,11 @@ "count": 2 } }, + "public/app/core/components/TimelineChart/TimelineChart.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/core/config.ts": { "no-barrel-files/no-barrel-files": { "count": 2 @@ -1347,11 +1563,27 @@ "public/app/features/admin/UserLdapSyncInfo.tsx": { "no-restricted-syntax": { "count": 3 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/admin/UserOrgs.tsx": { "no-restricted-syntax": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 3 + } + }, + "public/app/features/admin/UserProfile.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/admin/UserSessions.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/admin/ldap/LdapDrawer.tsx": { @@ -1616,6 +1848,9 @@ }, "no-restricted-syntax": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx": { @@ -1813,6 +2048,16 @@ "count": 1 } }, + "public/app/features/annotations/components/AnnotationResultMapper.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/annotations/components/StandardAnnotationQueryEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/annotations/events_processing.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -1856,6 +2101,36 @@ "count": 1 } }, + "public/app/features/canvas/elements/notFound.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/canvas/elements/rectangle.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/canvas/runtime/element.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/canvas/runtime/frame.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/canvas/runtime/root.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/canvas/runtime/scene.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/commandPalette/actions/scopeActions.tsx": { "react-hooks/rules-of-hooks": { "count": 4 @@ -1960,6 +2235,21 @@ "count": 2 } }, + "public/app/features/dashboard-scene/conditional-rendering/conditions/ConditionalRenderingData.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard-scene/conditional-rendering/conditions/ConditionalRenderingTimeRangeSize.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard-scene/conditional-rendering/conditions/ConditionalRenderingVariable.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard-scene/edit-pane/DashboardEditPaneRenderer.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -1970,6 +2260,11 @@ "count": 4 } }, + "public/app/features/dashboard-scene/edit-pane/DashboardEditableElement.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -1985,11 +2280,31 @@ "count": 1 } }, + "public/app/features/dashboard-scene/inspect/InspectDataTab.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx": { "no-restricted-syntax": { "count": 1 } }, + "public/app/features/dashboard-scene/inspect/InspectMetaDataTab.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard-scene/inspect/InspectQueryTab.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard-scene/inspect/InspectStatsTab.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard-scene/pages/DashboardScenePage.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 @@ -2003,11 +2318,26 @@ "count": 1 } }, + "public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataAlertingTab.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 } }, + "public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.test.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -2036,6 +2366,21 @@ "count": 1 } }, + "public/app/features/dashboard-scene/scene/AddLibraryPanelDrawer.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard-scene/scene/CustomTimeRangeCompare.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard-scene/scene/DashboardDataLayerSet.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -2046,6 +2391,11 @@ "count": 1 } }, + "public/app/features/dashboard-scene/scene/ViewPanelScene.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard-scene/scene/export/exporters.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 6 @@ -2105,6 +2455,11 @@ "count": 1 } }, + "public/app/features/dashboard-scene/serialization/custom-variables/SnapshotVariable.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -2286,6 +2641,11 @@ "count": 1 } }, + "public/app/features/dashboard/components/DashNav/DashNavTimeControls.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard/components/DashNav/index.ts": { "no-barrel-files/no-barrel-files": { "count": 1 @@ -2309,6 +2669,11 @@ "count": 1 } }, + "public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard/components/DashboardRow/index.ts": { "no-barrel-files/no-barrel-files": { "count": 1 @@ -2327,6 +2692,14 @@ "public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx": { "no-restricted-syntax": { "count": 5 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/dashboard/components/DashboardSettings/index.ts": { @@ -2349,6 +2722,11 @@ "count": 1 } }, + "public/app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor.tsx": { "@grafana/no-aria-label-selectors": { "count": 1 @@ -2358,11 +2736,32 @@ }, "no-restricted-syntax": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard/components/PanelEditor/OptionsPaneOptions.test.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/dashboard/components/PanelEditor/PanelEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard/components/PanelEditor/PanelEditorQueries.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard/components/PanelEditor/PanelHeaderCorner.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/dashboard/components/PanelEditor/VisualizationSelectPane.tsx": { @@ -2422,11 +2821,22 @@ }, "no-restricted-syntax": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/dashboard/components/ShareModal/ShareLink.tsx": { "no-restricted-syntax": { "count": 3 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard/components/ShareModal/ShareModal.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx": { @@ -2442,6 +2852,9 @@ "public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx": { "no-restricted-syntax": { "count": 4 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/dashboard/components/ShareModal/ThemePicker.tsx": { @@ -2462,6 +2875,11 @@ "count": 1 } }, + "public/app/features/dashboard/components/SubMenu/SubMenu.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx": { "no-restricted-syntax": { "count": 1 @@ -2480,6 +2898,9 @@ "public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/dashboard/components/VersionHistory/useDashboardRestore.tsx": { @@ -2490,6 +2911,11 @@ "count": 1 } }, + "public/app/features/dashboard/containers/DashboardPage.test.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard/containers/DashboardPage.tsx": { "@grafana/no-aria-label-selectors": { "count": 1 @@ -2499,6 +2925,9 @@ }, "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/dashboard/containers/DashboardPageProxy.tsx": { @@ -2511,6 +2940,26 @@ "count": 1 } }, + "public/app/features/dashboard/containers/SoloPanelPage.test.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard/dashgrid/DashboardGrid.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard/dashgrid/DashboardPanel.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dashboard/dashgrid/PanelStateWrapper.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/dashboard/dashgrid/SeriesVisibilityConfigFactory.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -2621,6 +3070,11 @@ "count": 1 } }, + "public/app/features/datasources/components/DataSourcePluginSettings.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/datasources/components/DataSourcePluginState.tsx": { "no-restricted-syntax": { "count": 3 @@ -2709,6 +3163,14 @@ "public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/dimensions/editors/ThresholdsEditor/thresholds.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/dimensions/editors/URLPickerTab.tsx": { @@ -2741,21 +3203,66 @@ "count": 4 } }, + "public/app/features/explore/Explore.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/explore/ExploreTimeControls.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/explore/Logs/LiveLogs.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/explore/Logs/LogsColumnSearch.tsx": { "no-restricted-syntax": { "count": 1 } }, + "public/app/features/explore/Logs/LogsContainer.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/explore/RawPrometheus/RawPrometheusContainer.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx": { "no-restricted-syntax": { "count": 1 } }, + "public/app/features/explore/Table/TableContainer.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/explore/TraceView/TraceView.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 } }, + "public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/ViewingLayer.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/index.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx": { "react-hooks/rules-of-hooks": { "count": 1 @@ -2766,26 +3273,71 @@ "count": 1 } }, + "public/app/features/explore/TraceView/components/TraceTimelineViewer/ListView/index.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/SpanDetailLinkButtons.tsx": { "react-hooks/rules-of-hooks": { "count": 1 } }, + "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanLinks.tsx": { "no-restricted-syntax": { "count": 1 } }, + "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/index.tsx": { "no-barrel-files/no-barrel-files": { "count": 1 } }, + "public/app/features/explore/TraceView/components/TraceTimelineViewer/VirtualizedTraceView.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/explore/TraceView/components/TraceTimelineViewer/index.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/explore/TraceView/components/TraceTimelineViewer/utils.tsx": { "no-barrel-files/no-barrel-files": { "count": 1 } }, + "public/app/features/explore/TraceView/components/common/SearchBarInput.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/explore/TraceView/components/demo/trace-generators.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -2809,6 +3361,21 @@ "count": 1 } }, + "public/app/features/explore/TraceView/components/utils/DraggableManager/demo/DividerDemo.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/explore/TraceView/components/utils/DraggableManager/demo/DraggableManagerDemo.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/explore/TraceView/components/utils/DraggableManager/demo/RegionDemo.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/explore/TraceView/components/utils/DraggableManager/demo/index.tsx": { "no-barrel-files/no-barrel-files": { "count": 1 @@ -2886,6 +3453,9 @@ "public/app/features/inspector/InspectDataTab.tsx": { "@grafana/no-aria-label-selectors": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/inspector/InspectJSONTab.tsx": { @@ -2904,6 +3474,19 @@ }, "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/invites/InviteeRow.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/invites/InviteesTable.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/invites/SignupInvited.tsx": { @@ -2916,6 +3499,11 @@ "count": 2 } }, + "public/app/features/live/LiveConnectionWarning.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/live/centrifuge/LiveDataStream.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -2926,6 +3514,21 @@ "count": 1 } }, + "public/app/features/logs/components/LogDetails.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/logs/components/LogDetailsRow.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/logs/components/LogMessageAnsi.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/logs/logsFrame.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -2942,6 +3545,9 @@ "public/app/features/manage-dashboards/DashboardImportPage.tsx": { "no-restricted-syntax": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/manage-dashboards/components/ImportDashboardForm.tsx": { @@ -2957,6 +3563,11 @@ "count": 1 } }, + "public/app/features/manage-dashboards/components/ImportDashboardOverview.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx": { "no-restricted-syntax": { "count": 1 @@ -3013,6 +3624,11 @@ "count": 1 } }, + "public/app/features/org/OrgDetailsPage.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/org/OrgProfile.tsx": { "no-restricted-syntax": { "count": 1 @@ -3028,6 +3644,11 @@ "count": 1 } }, + "public/app/features/panel/components/PanelPluginError.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 3 + } + }, "public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx": { "@grafana/no-aria-label-selectors": { "count": 1 @@ -3076,6 +3697,11 @@ "count": 1 } }, + "public/app/features/plugins/admin/components/PluginDashboards.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/plugins/admin/components/PluginDetailsPage.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -3104,6 +3730,11 @@ "count": 1 } }, + "public/app/features/plugins/components/AppRootPage.test.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/plugins/datasource_srv.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 @@ -3147,11 +3778,26 @@ "count": 3 } }, + "public/app/features/profile/UserOrganizations.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/profile/UserProfileEditForm.tsx": { "no-restricted-syntax": { "count": 3 } }, + "public/app/features/profile/UserSessions.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/profile/UserTeams.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/provisioning/Config/ConfigFormGithubCollapse.tsx": { "no-restricted-syntax": { "count": 2 @@ -3189,6 +3835,9 @@ }, "react-hooks/rules-of-hooks": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/query/components/QueryEditorRowHeader.tsx": { @@ -3196,9 +3845,17 @@ "count": 1 } }, + "public/app/features/query/components/QueryEditorRows.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/query/components/QueryGroup.tsx": { "@grafana/no-aria-label-selectors": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts": { @@ -3287,6 +3944,11 @@ "count": 2 } }, + "public/app/features/teams/TeamGroupSync.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/teams/TeamSettings.tsx": { "no-restricted-syntax": { "count": 4 @@ -3360,11 +4022,21 @@ "count": 1 } }, + "public/app/features/transformers/editors/ConcatenateTransformerEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 } }, + "public/app/features/transformers/editors/FilterByNameTransformerEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/transformers/editors/GroupToNestedTableTransformerEditor.tsx": { "no-restricted-syntax": { "count": 1 @@ -3375,6 +4047,11 @@ "count": 1 } }, + "public/app/features/transformers/editors/RenameByRegexTransformer.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/transformers/editors/SortByTransformerEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -3429,9 +4106,17 @@ "count": 3 } }, + "public/app/features/variables/adhoc/AdHocVariableEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/variables/adhoc/picker/AdHocFilter.tsx": { "no-restricted-syntax": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx": { @@ -3449,24 +4134,52 @@ "count": 1 } }, + "public/app/features/variables/adhoc/picker/AdHocPicker.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/variables/adhoc/picker/ConditionSegment.tsx": { "no-restricted-syntax": { "count": 2 } }, + "public/app/features/variables/constant/ConstantVariableEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/variables/constant/reducer.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 } }, + "public/app/features/variables/custom/CustomVariableEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/variables/custom/reducer.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 } }, + "public/app/features/variables/datasource/DataSourceVariableEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/variables/editor/VariableEditorContainer.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/features/variables/editor/VariableEditorEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/variables/editor/VariableEditorList.tsx": { @@ -3497,6 +4210,11 @@ "count": 4 } }, + "public/app/features/variables/pickers/OptionsPicker/OptionsPicker.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/features/variables/pickers/OptionsPicker/actions.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 @@ -3518,16 +4236,25 @@ "public/app/features/variables/pickers/shared/VariableInput.tsx": { "no-restricted-syntax": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/variables/pickers/shared/VariableOptions.tsx": { "@grafana/no-aria-label-selectors": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/variables/query/QueryVariableEditor.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/features/variables/query/QueryVariableRefreshSelect.tsx": { @@ -3658,6 +4385,11 @@ "count": 1 } }, + "public/app/plugins/datasource/azuremonitor/components/ConfigEditor/ConfigEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/datasource/azuremonitor/components/ConfigEditor/CurrentUserFallbackCredentials.tsx": { "no-restricted-syntax": { "count": 2 @@ -3751,6 +4483,16 @@ "count": 1 } }, + "public/app/plugins/datasource/cloud-monitoring/components/CloudMonitoringCheatSheet.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/datasource/cloud-monitoring/components/Fields.tsx": { "no-restricted-syntax": { "count": 1 @@ -3759,6 +4501,9 @@ "public/app/plugins/datasource/cloud-monitoring/components/VariableQueryEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/plugins/datasource/cloud-monitoring/datasource.ts": { @@ -3945,6 +4690,11 @@ "count": 1 } }, + "public/app/plugins/datasource/grafana-testdata-datasource/ConfigEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 3 @@ -3956,6 +4706,16 @@ "count": 1 } }, + "public/app/plugins/datasource/grafana-testdata-datasource/TestInfoTab.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/plugins/datasource/grafana-testdata-datasource/components/CSVWaveEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/datasource/grafana-testdata-datasource/components/RandomWalkEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 @@ -3990,6 +4750,9 @@ "public/app/plugins/datasource/grafana/components/QueryEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/plugins/datasource/grafana/components/TimeRegionEditor.tsx": { @@ -4005,9 +4768,17 @@ "count": 1 } }, + "public/app/plugins/datasource/graphite/components/MetricTankMetaInspector.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/datasource/graphite/configuration/ConfigEditor.tsx": { "no-restricted-syntax": { "count": 3 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/plugins/datasource/graphite/datasource.ts": { @@ -4085,6 +4856,9 @@ }, "no-restricted-syntax": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/plugins/datasource/influxdb/components/editor/config/InfluxSQLConfig.tsx": { @@ -4100,6 +4874,14 @@ "public/app/plugins/datasource/influxdb/components/editor/query/flux/FluxQueryEditor.tsx": { "no-restricted-syntax": { "count": 3 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/plugins/datasource/influxdb/components/editor/query/fsql/FSQLEditor.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/plugins/datasource/influxdb/components/editor/query/influxql/visual/PartListSection.tsx": { @@ -4150,11 +4932,21 @@ "count": 2 } }, + "public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/datasource/loki/components/LokiContextUi.tsx": { "no-restricted-syntax": { "count": 7 } }, + "public/app/plugins/datasource/loki/components/LokiLabelBrowser.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx": { "no-restricted-syntax": { "count": 2 @@ -4163,6 +4955,9 @@ "public/app/plugins/datasource/loki/components/LokiQueryField.tsx": { "no-restricted-syntax": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/plugins/datasource/loki/configuration/DerivedField.tsx": { @@ -4270,6 +5065,11 @@ "count": 15 } }, + "public/app/plugins/datasource/tempo/QueryField.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/datasource/tempo/ServiceGraphSection.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -4278,6 +5078,9 @@ "public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilter.tsx": { "no-restricted-syntax": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/plugins/datasource/tempo/_importedDependencies/components/AdHocFilter/AdHocFilterKey.tsx": { @@ -4350,6 +5153,11 @@ "count": 1 } }, + "public/app/plugins/panel/annolist/AnnoListPanel.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/panel/annolist/AnnotationListItem.tsx": { "no-restricted-syntax": { "count": 1 @@ -4365,6 +5173,11 @@ "count": 1 } }, + "public/app/plugins/panel/bargauge/BarGaugePanel.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/panel/candlestick/CandlestickPanel.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -4378,6 +5191,21 @@ "count": 8 } }, + "public/app/plugins/panel/canvas/CanvasPanel.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/plugins/panel/canvas/components/connections/Connections.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/plugins/panel/canvas/components/connections/Connections2.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/panel/canvas/editor/LineStyleEditor.tsx": { "no-restricted-syntax": { "count": 1 @@ -4404,11 +5232,27 @@ }, "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/plugins/panel/debug/DebugPanel.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/plugins/panel/debug/EventBusLogger.tsx": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/plugins/panel/debug/RenderInfoViewer.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/plugins/panel/debug/StateView.tsx": { @@ -4421,9 +5265,22 @@ "count": 1 } }, + "public/app/plugins/panel/gauge/GaugePanel.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, + "public/app/plugins/panel/geomap/GeomapPanel.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/panel/geomap/components/DebugOverlay.tsx": { "@grafana/no-aria-label-selectors": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/plugins/panel/geomap/components/MarkersLegend.tsx": { @@ -4434,6 +5291,11 @@ "count": 1 } }, + "public/app/plugins/panel/geomap/components/ObservablePropsWrapper.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -4482,6 +5344,11 @@ "count": 2 } }, + "public/app/plugins/panel/gettingstarted/GettingStarted.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/panel/heatmap/HeatmapPanel.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -4516,6 +5383,11 @@ "count": 17 } }, + "public/app/plugins/panel/histogram/Histogram.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/panel/live/LiveChannelEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 @@ -4527,6 +5399,9 @@ "public/app/plugins/panel/live/LivePanel.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 + }, + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 } }, "public/app/plugins/panel/nodeGraph/Edge.tsx": { @@ -4569,6 +5444,11 @@ "count": 1 } }, + "public/app/plugins/panel/stat/StatPanel.tsx": { + "react-prefer-function-component/react-prefer-function-component": { + "count": 1 + } + }, "public/app/plugins/panel/state-timeline/migrations.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 diff --git a/eslint.config.js b/eslint.config.js index cb8fba5222d..417d8e552b2 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,6 +8,7 @@ const jsxA11yPlugin = require('eslint-plugin-jsx-a11y'); const lodashPlugin = require('eslint-plugin-lodash'); const barrelPlugin = require('eslint-plugin-no-barrel-files'); const reactPlugin = require('eslint-plugin-react'); +const reactPreferFunctionComponentPlugin = require('eslint-plugin-react-prefer-function-component'); const testingLibraryPlugin = require('eslint-plugin-testing-library'); const unicornPlugin = require('eslint-plugin-unicorn'); @@ -140,6 +141,7 @@ module.exports = [ 'no-barrel-files': barrelPlugin, '@grafana': grafanaPlugin, unicorn: unicornPlugin, + 'react-prefer-function-component': reactPreferFunctionComponentPlugin, }, settings: { @@ -156,6 +158,7 @@ module.exports = [ '@grafana/no-border-radius-literal': 'error', '@grafana/no-unreduced-motion': 'error', '@grafana/no-restricted-img-srcs': 'error', + 'react-prefer-function-component/react-prefer-function-component': 'error', 'react/prop-types': 'off', // need to ignore emotion's `css` prop, see https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md#rule-options 'react/no-unknown-property': ['error', { ignore: ['css'] }], diff --git a/package.json b/package.json index 68aa8df691e..39fc401989a 100644 --- a/package.json +++ b/package.json @@ -198,6 +198,7 @@ "eslint-plugin-no-barrel-files": "^1.1.1", "eslint-plugin-react": "7.37.5", "eslint-plugin-react-hooks": "5.2.0", + "eslint-plugin-react-prefer-function-component": "4.0.1", "eslint-plugin-testing-library": "^7.0.0", "eslint-plugin-unicorn": "^56.0.0", "eslint-scope": "^8.1.0", diff --git a/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx b/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx index 14f1a9082be..b57a795c3b5 100644 --- a/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx +++ b/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx @@ -108,11 +108,15 @@ describe('Plugin Extensions / Utils', () => { test('should work when using class components', () => { const props = {}; + // this specifically needs to be a class component + // eslint-disable-next-line react-prefer-function-component/react-prefer-function-component const Component1 = class extends React.Component<{}> { render() { return <div>Test 1</div>; } }; + // this specifically needs to be a class component + // eslint-disable-next-line react-prefer-function-component/react-prefer-function-component const Component2 = class extends React.Component<{}> { render() { return <div>Test 2</div>; diff --git a/yarn.lock b/yarn.lock index 6678a75a407..87d610fc52c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16321,6 +16321,13 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-react-prefer-function-component@npm:4.0.1": + version: 4.0.1 + resolution: "eslint-plugin-react-prefer-function-component@npm:4.0.1" + checksum: 10/dfb1ceb098342320cc82429e227db91a72e7e4875795913fb481beb64669080354279a4ad9a3c53060b390825c7d08c352f938f121ef1acb40da6d969199c1b0 + languageName: node + linkType: hard + "eslint-plugin-react@npm:7.37.5": version: 7.37.5 resolution: "eslint-plugin-react@npm:7.37.5" @@ -18445,6 +18452,7 @@ __metadata: eslint-plugin-no-barrel-files: "npm:^1.1.1" eslint-plugin-react: "npm:7.37.5" eslint-plugin-react-hooks: "npm:5.2.0" + eslint-plugin-react-prefer-function-component: "npm:4.0.1" eslint-plugin-testing-library: "npm:^7.0.0" eslint-plugin-unicorn: "npm:^56.0.0" eslint-scope: "npm:^8.1.0" From 1c614fdc9a8873b2d42c29f005e69fad2cbbb5aa Mon Sep 17 00:00:00 2001 From: Matheus Macabu <macabu@users.noreply.github.com> Date: Wed, 15 Oct 2025 12:17:53 +0200 Subject: [PATCH 23/75] Docs: Add batch_wait_duration and batch_size_bytes to usage insights Loki exporter (#112408) * Docs: Add batch_wait_duration and batch_size_bytes to usage insights Loki exporter * Run linter --- .../enterprise-configuration/index.md | 16 ++++++++++++++++ .../configure-security/export-logs.md | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md b/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md index c7570c71eaa..82eec98249a 100644 --- a/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md +++ b/docs/sources/setup-grafana/configure-grafana/enterprise-configuration/index.md @@ -112,6 +112,22 @@ Decide whether or not to enable the TLS (Transport Layer Security) protocol when Set the tenant ID for Loki communication, which is disabled by default. The tenant ID is required to interact with Loki running in [multi-tenant mode](/docs/loki/latest/operations/multi-tenancy/). +### batch_wait_duration + +How long to wait before sending a request to Loki with the batch of events. Uses duration format: e.g. 5s, 1m. Defaults to 5s. + +Whatever happens first between `batch_wait_duration` and `batch_size_bytes` will trigger the batch to be sent to Loki. + +If the wait duration is very long and the `batch_size_bytes` is very high, events may take a long time to be sent. + +### batch_size_bytes + +How many events (in bytes) to accumulate in a single batch before sending it to Loki. Defaults to 100 KiB. + +Whatever happens first between `batch_wait_duration` and `batch_size_bytes` will trigger the batch to be sent to Loki. + +If you wish to always wait for the `batch_wait_duration`, set this to a very high number. + ## [analytics.summaries] ### buffer_write_interval diff --git a/docs/sources/setup-grafana/configure-security/export-logs.md b/docs/sources/setup-grafana/configure-security/export-logs.md index f55336f8a67..e2ca4ad221c 100644 --- a/docs/sources/setup-grafana/configure-security/export-logs.md +++ b/docs/sources/setup-grafana/configure-security/export-logs.md @@ -94,6 +94,14 @@ tls = true # Set the tenant ID for Loki communication, which is disabled by default. # The tenant ID is required to interact with Loki running in multi-tenant mode. tenant_id = +# How long to wait before sending a request to Loki with the batch of events. Uses duration format: e.g. 5s, 1m +# Whatever happens first between `batch_wait_duration` and `batch_size_bytes` will trigger the batch to be sent to Loki. +# If the wait duration is very long and the `batch_size_bytes` is very high, events may take a long time to be sent. +batch_wait_duration = 5s +# How many events (in bytes) to accumulate in a single batch before sending it to Loki. +# Whatever happens first between `batch_wait_duration` and `batch_size_bytes` will trigger the batch to be sent to Loki. +# If you wish to always wait for the `batch_wait_duration`, set this to a very high number. +batch_size_bytes = 102400 # 100KiB ``` Using `logger` will print usage insights to your [Grafana server log](../../configure-grafana/#log). From e290c92e1bb232d71041129f2e2163ba1bec71f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0tibran=C3=BD?= <pstibrany@gmail.com> Date: Wed, 15 Oct 2025 12:33:04 +0200 Subject: [PATCH 24/75] Convert unique keys in file and file_meta tables into primary key. (#112269) * Convert unique keys in file and file_meta tables into primary key. * Fix panic. * Fix comment. * Always add migration to drop auto-generated PK. * Drop and create PK for mysql in single statement. * Drop my_row_id column too. (Please drop primary key column to be able to drop generated invisible primary key.) --- pkg/infra/filestorage/fs_integration_test.go | 3 - .../sqlstore/migrations/db_file_storage.go | 133 +++++++++++++++++- 2 files changed, 132 insertions(+), 4 deletions(-) diff --git a/pkg/infra/filestorage/fs_integration_test.go b/pkg/infra/filestorage/fs_integration_test.go index 6909aa94a1d..96963ba1452 100644 --- a/pkg/infra/filestorage/fs_integration_test.go +++ b/pkg/infra/filestorage/fs_integration_test.go @@ -176,9 +176,6 @@ func runTests(createCases func() []fsTestCase, t *testing.T) { } func TestIntegrationFsStorage(t *testing.T) { - if true { - t.Skip("flakey tests - skipping") - } testutil.SkipIntegrationTestInShortMode(t) //skipTest := true diff --git a/pkg/services/sqlstore/migrations/db_file_storage.go b/pkg/services/sqlstore/migrations/db_file_storage.go index fcc49ae0d57..2486e7dc478 100644 --- a/pkg/services/sqlstore/migrations/db_file_storage.go +++ b/pkg/services/sqlstore/migrations/db_file_storage.go @@ -1,6 +1,8 @@ package migrations -import "github.com/grafana/grafana/pkg/services/sqlstore/migrator" +import ( + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" +) func addDbFileStorageMigration(mg *migrator.Migrator) { filesTable := migrator.Table{ @@ -67,4 +69,133 @@ func addDbFileStorageMigration(mg *migrator.Migrator) { mg.AddMigration("migrate contents column to mediumblob for MySQL", migrator.NewRawSQLMigration(""). Mysql("ALTER TABLE file MODIFY contents MEDIUMBLOB;")) + + convertFilePathHashIndexToPrimaryKey(mg) + convertFileMetaPathHashKeyIndexToPrimaryKey(mg) +} + +func deleteAutogeneratedIds(mg *migrator.Migrator) bool { + // Check if delete_auto_gen_ids is enabled in the configuration + if mg.Cfg == nil || mg.Cfg.Raw == nil { + return false + } + + deleteAutoGenIDs := mg.Cfg.Raw.Section("database").Key("delete_auto_gen_ids").MustBool(false) + return deleteAutoGenIDs +} + +// This converts the existing unique constraint UQE_file_path_hash to a primary key in file table +func convertFilePathHashIndexToPrimaryKey(mg *migrator.Migrator) { + // Run the migration to drop the auto-generated "my_row_id" primary key + // Auto-generated primary keys are a MySQL feature, so we don't need to do anything for Postgres or SQLite + mysql := ` + ALTER TABLE file + DROP INDEX UQE_file_path_hash, + ADD PRIMARY KEY (path_hash); + ` + if deleteAutogeneratedIds(mg) { + mysql = ` + ALTER TABLE file + DROP PRIMARY KEY, + DROP COLUMN my_row_id, + DROP INDEX UQE_file_path_hash, + ADD PRIMARY KEY (path_hash); + ` + } + + migration := migrator.NewRawSQLMigration(""). + Mysql(mysql). + Postgres(` + DO $$ + BEGIN + -- Drop the unique constraint if it exists + DROP INDEX IF EXISTS "UQE_file_path_hash"; + + -- Add primary key if it doesn't already exist + IF NOT EXISTS (SELECT 1 FROM pg_index i WHERE indrelid = 'file'::regclass AND indisprimary) THEN + ALTER TABLE file ADD PRIMARY KEY (path_hash); + END IF; + END $$; + `).SQLite(` + -- For SQLite we need to recreate the table with primary key. CREATE TABLE was generated by ".schema file" command after running migration. + CREATE TABLE file_new + ( + path TEXT NOT NULL, + path_hash TEXT NOT NULL, + parent_folder_path_hash TEXT NOT NULL, + contents BLOB NOT NULL, + etag TEXT NOT NULL, + cache_control TEXT NOT NULL, + content_disposition TEXT NOT NULL, + updated DATETIME NOT NULL, + created DATETIME NOT NULL, + size INTEGER NOT NULL, + mime_type TEXT NOT NULL, + + PRIMARY KEY (path_hash) + ); + + INSERT INTO file_new (path, path_hash, parent_folder_path_hash, contents, etag, cache_control, content_disposition, updated, created, size, mime_type) + SELECT path, path_hash, parent_folder_path_hash, contents, etag, cache_control, content_disposition, updated, created, size, mime_type FROM file; + + DROP TABLE file; + ALTER TABLE file_new RENAME TO file; + + CREATE INDEX IDX_file_parent_folder_path_hash ON file (parent_folder_path_hash); + `) + + mg.AddMigration("add primary key to file table", migration) +} + +// This converts the existing unique constraint UQE_file_meta_path_hash_key to a primary key in file_meta table +func convertFileMetaPathHashKeyIndexToPrimaryKey(mg *migrator.Migrator) { + // Run the migration to drop the auto-generated "my_row_id" primary key + // Auto-generated primary keys are a MySQL feature, so we don't need to do anything for Postgres or SQLite + mysql := ` + ALTER TABLE file_meta + DROP INDEX UQE_file_meta_path_hash_key, + ADD PRIMARY KEY (path_hash, ` + "`key`" + `); + ` + if deleteAutogeneratedIds(mg) { + mysql = ` + ALTER TABLE file_meta + DROP PRIMARY KEY, + DROP COLUMN my_row_id, + DROP INDEX UQE_file_meta_path_hash_key, + ADD PRIMARY KEY (path_hash, ` + "`key`" + `); + ` + } + + migration := migrator.NewRawSQLMigration(""). + Mysql(mysql). + Postgres(` + DO $$ + BEGIN + -- Drop the unique constraint if it exists + DROP INDEX IF EXISTS "UQE_file_meta_path_hash_key"; + + -- Add primary key if it doesn't already exist + IF NOT EXISTS (SELECT 1 FROM pg_index i WHERE indrelid = 'file_meta'::regclass AND indisprimary) THEN + ALTER TABLE file_meta ADD PRIMARY KEY (path_hash, ` + "`key`" + `); + END IF; + END $$; + `).SQLite(` + -- For SQLite we need to recreate the table with primary key. CREATE TABLE was generated by ".schema file_meta" command after running migration. + CREATE TABLE file_meta_new + ( + path_hash TEXT NOT NULL, + key TEXT NOT NULL, + value TEXT NOT NULL, + + PRIMARY KEY (path_hash, key) + ); + + INSERT INTO file_meta_new (path_hash, key, value) + SELECT path_hash, key, value FROM file_meta; + + DROP TABLE file_meta; + ALTER TABLE file_meta_new RENAME TO file_meta; + `) + + mg.AddMigration("add primary key to file_meta table", migration) } From e32694b0d0dfe82af75ff49612e1f14a3525c83a Mon Sep 17 00:00:00 2001 From: Costa Alexoglou <konstantinos.alexoglou@grafana.com> Date: Wed, 15 Oct 2025 12:33:47 +0200 Subject: [PATCH 25/75] chore: execute devenv from any directory (#112406) --- devenv/create_docker_compose.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/devenv/create_docker_compose.sh b/devenv/create_docker_compose.sh index 2acdd0836a1..5d1d2a90bb4 100755 --- a/devenv/create_docker_compose.sh +++ b/devenv/create_docker_compose.sh @@ -2,15 +2,18 @@ shopt -s nullglob # Enable nullglob -blocks_dir=docker/blocks -docker_dir=docker -template_dir=templates +# Get the directory where this script is located (works from any execution directory) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +blocks_dir="${SCRIPT_DIR}/docker/blocks" +docker_dir="${SCRIPT_DIR}/docker" +template_dir="${SCRIPT_DIR}/templates" grafana_config_file=conf.tmp grafana_config=config -compose_header_file=docker/compose_header.yml -compose_volume_section_file=docker/compose_volume_section.yml +compose_header_file="${SCRIPT_DIR}/docker/compose_header.yml" +compose_volume_section_file="${SCRIPT_DIR}/docker/compose_volume_section.yml" compose_volume_section_create_flag=docker_volume_create_true compose_file=docker-compose.yaml env_file=.env From 032502b5b777f81a45ce784d2bd0bfbcc3bf20d5 Mon Sep 17 00:00:00 2001 From: Ashley Harrison <ashley.harrison@grafana.com> Date: Wed, 15 Oct 2025 11:41:36 +0100 Subject: [PATCH 26/75] Chore: regenerate theme schema + ignore in prettier (#112414) regenerate theme schema + ignore in prettier --- .prettierignore | 1 + .../theme-playground/schema.generated.json | 1030 +++++++++-------- 2 files changed, 536 insertions(+), 495 deletions(-) diff --git a/.prettierignore b/.prettierignore index 726f8613700..7d9575558d9 100644 --- a/.prettierignore +++ b/.prettierignore @@ -26,6 +26,7 @@ vendor # Auto-generated theme files theme.light.generated.json theme.dark.generated.json +public/app/features/theme-playground/schema.generated.json # Generated Swagger API specs public/api-merged.json diff --git a/public/app/features/theme-playground/schema.generated.json b/public/app/features/theme-playground/schema.generated.json index 1b1850a10f3..936471ba6ea 100644 --- a/public/app/features/theme-playground/schema.generated.json +++ b/public/app/features/theme-playground/schema.generated.json @@ -1,511 +1,551 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "DeepPartial<ThemeColorsBase<ThemeRichColor>>": { - "properties": { - "action": { - "$ref": "#/definitions/DeepPartial<{selected:string;selectedBorder:string;hover:string;hoverOpacity:number;focus:string;disabledBackground:string;disabledText:string;disabledOpacity:number;}>" - }, - "background": { - "$ref": "#/definitions/DeepPartial<{canvas:string;primary:string;secondary:string;elevated:string;}>" - }, - "border": { - "$ref": "#/definitions/DeepPartial<{weak:string;medium:string;strong:string;}>" - }, - "contrastThreshold": { - "type": "number" - }, - "error": { - "$ref": "#/definitions/DeepPartial<ThemeRichColor>" - }, - "gradients": { - "$ref": "#/definitions/DeepPartial<{brandVertical:string;brandHorizontal:string;}>" - }, - "hoverFactor": { - "type": "number" - }, - "info": { - "$ref": "#/definitions/DeepPartial<ThemeRichColor>" - }, - "mode": { - "enum": ["dark", "light"], - "type": "string" - }, - "primary": { - "$ref": "#/definitions/DeepPartial<ThemeRichColor>" - }, - "secondary": { - "$ref": "#/definitions/DeepPartial<ThemeRichColor>" - }, - "success": { - "$ref": "#/definitions/DeepPartial<ThemeRichColor>" - }, - "text": { - "$ref": "#/definitions/DeepPartial<{primary:string;secondary:string;disabled:string;link:string;maxContrast:string;}>" - }, - "tonalOffset": { - "type": "number" - }, - "warning": { - "$ref": "#/definitions/DeepPartial<ThemeRichColor>" - } - }, - "type": "object" - }, - "DeepPartial<ThemeRichColor>": { - "properties": { - "border": { - "description": "Used for borders", - "type": "string" - }, - "borderTransparent": { - "description": "Used for weak colored borders like larger alert/banner boxes and smaller badges and tags", - "type": "string" - }, - "contrastText": { - "description": "Text color for text ontop of main", - "type": "string" - }, - "main": { - "description": "Main color", - "type": "string" - }, - "name": { - "description": "color intent (primary, secondary, info, error, etc)", - "type": "string" - }, - "shade": { - "description": "Used for hover", - "type": "string" - }, - "text": { - "description": "Used for text", - "type": "string" - }, - "transparent": { - "description": "Used subtly colored backgrounds", - "type": "string" - } - }, - "type": "object" - }, - "DeepPartial<{brandVertical:string;brandHorizontal:string;}>": { - "properties": { - "brandHorizontal": { - "type": "string" - }, - "brandVertical": { - "type": "string" - } - }, - "type": "object" - }, - "DeepPartial<{canvas:string;primary:string;secondary:string;elevated:string;}>": { - "properties": { - "canvas": { - "description": "Dashboard and body background", - "type": "string" - }, - "elevated": { - "description": "For popovers and menu backgrounds. This is the same color as primary in most light themes but in dark\nthemes it has a brighter shade to help give it contrast against the primary background.", - "type": "string" - }, - "primary": { - "description": "Primary content pane background (panels etc)", - "type": "string" - }, - "secondary": { - "description": "Cards and elements that need to stand out on the primary background", - "type": "string" - } - }, - "type": "object" - }, - "DeepPartial<{primary:string;secondary:string;disabled:string;link:string;maxContrast:string;}>": { - "properties": { - "disabled": { - "type": "string" - }, - "link": { - "type": "string" - }, - "maxContrast": { - "description": "Used for auto white or dark text on colored backgrounds", - "type": "string" - }, - "primary": { - "type": "string" - }, - "secondary": { - "type": "string" - } - }, - "type": "object" - }, - "DeepPartial<{selected:string;selectedBorder:string;hover:string;hoverOpacity:number;focus:string;disabledBackground:string;disabledText:string;disabledOpacity:number;}>": { - "properties": { - "disabledBackground": { - "description": "Used for disabled buttons and inputs", - "type": "string" - }, - "disabledOpacity": { - "description": "Disablerd opacity", - "type": "number" - }, - "disabledText": { - "description": "Disabled text", - "type": "string" - }, - "focus": { - "description": "Used focused menu item / select option", - "type": "string" - }, - "hover": { - "description": "Used for hovered menu item / select option", - "type": "string" - }, - "hoverOpacity": { - "description": "Used for button/colored background hover opacity", - "type": "number" - }, - "selected": { - "description": "Used for selected menu item / select option", - "type": "string" - }, - "selectedBorder": { - "type": "string" - } - }, - "type": "object" - }, - "DeepPartial<{weak:string;medium:string;strong:string;}>": { - "properties": { - "medium": { - "type": "string" - }, - "strong": { - "type": "string" - }, - "weak": { - "type": "string" - } - }, - "type": "object" - }, - "ThemeShapeInput": { - "properties": { - "borderRadius": { - "type": "number" - } - }, - "type": "object" - }, - "ThemeTypographyInput": { - "properties": { - "fontFamily": { - "type": "string" - }, - "fontFamilyMonospace": { - "type": "string" - }, - "fontSize": { - "type": "number" - }, - "fontWeightBold": { - "type": "number" - }, - "fontWeightLight": { - "type": "number" - }, - "fontWeightMedium": { - "type": "number" - }, - "fontWeightRegular": { - "type": "number" - }, - "htmlFontSize": { - "type": "number" - } - }, - "type": "object" - }, - "ThemeVizColor<\"blue\">": { - "properties": { - "aliases": { - "items": { - "type": "string" - }, - "type": "array" - }, - "color": { - "type": "string" - }, - "name": { - "$ref": "#/definitions/ThemeVizColorShadeName_4" - }, - "primary": { - "type": "boolean" - } - }, - "type": "object" - }, - "ThemeVizColor<\"green\">": { - "properties": { - "aliases": { - "items": { - "type": "string" - }, - "type": "array" - }, - "color": { - "type": "string" - }, - "name": { - "$ref": "#/definitions/ThemeVizColorShadeName_3" - }, - "primary": { - "type": "boolean" - } - }, - "type": "object" - }, - "ThemeVizColor<\"orange\">": { - "properties": { - "aliases": { - "items": { - "type": "string" - }, - "type": "array" - }, - "color": { - "type": "string" - }, - "name": { - "$ref": "#/definitions/ThemeVizColorShadeName_1" - }, - "primary": { - "type": "boolean" - } - }, - "type": "object" - }, - "ThemeVizColor<\"purple\">": { - "properties": { - "aliases": { - "items": { - "type": "string" - }, - "type": "array" - }, - "color": { - "type": "string" - }, - "name": { - "$ref": "#/definitions/ThemeVizColorShadeName_5" - }, - "primary": { - "type": "boolean" - } - }, - "type": "object" - }, - "ThemeVizColor<\"red\">": { - "properties": { - "aliases": { - "items": { - "type": "string" - }, - "type": "array" - }, - "color": { - "type": "string" - }, - "name": { - "$ref": "#/definitions/ThemeVizColorShadeName" - }, - "primary": { - "type": "boolean" - } - }, - "type": "object" - }, - "ThemeVizColor<\"yellow\">": { - "properties": { - "aliases": { - "items": { - "type": "string" - }, - "type": "array" - }, - "color": { - "type": "string" - }, - "name": { - "$ref": "#/definitions/ThemeVizColorShadeName_2" - }, - "primary": { - "type": "boolean" - } - }, - "type": "object" - }, - "ThemeVizColorShadeName": { - "enum": ["dark-red", "light-red", "red", "semi-dark-red", "super-light-red"], - "type": "string" - }, - "ThemeVizColorShadeName_1": { - "enum": ["dark-orange", "light-orange", "orange", "semi-dark-orange", "super-light-orange"], - "type": "string" - }, - "ThemeVizColorShadeName_2": { - "enum": ["dark-yellow", "light-yellow", "semi-dark-yellow", "super-light-yellow", "yellow"], - "type": "string" - }, - "ThemeVizColorShadeName_3": { - "enum": ["dark-green", "green", "light-green", "semi-dark-green", "super-light-green"], - "type": "string" - }, - "ThemeVizColorShadeName_4": { - "enum": ["blue", "dark-blue", "light-blue", "semi-dark-blue", "super-light-blue"], - "type": "string" - }, - "ThemeVizColorShadeName_5": { - "enum": ["dark-purple", "light-purple", "purple", "semi-dark-purple", "super-light-purple"], - "type": "string" - }, - "ThemeVizHue": { - "anyOf": [ - { - "properties": { - "name": { - "const": "red", - "type": "string" + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DeepPartial<ThemeColorsBase<ThemeRichColor>>": { + "properties": { + "action": { + "$ref": "#/definitions/DeepPartial<{selected:string;selectedBorder:string;hover:string;hoverOpacity:number;focus:string;disabledBackground:string;disabledText:string;disabledOpacity:number;}>" + }, + "background": { + "$ref": "#/definitions/DeepPartial<{canvas:string;primary:string;secondary:string;elevated:string;}>" + }, + "border": { + "$ref": "#/definitions/DeepPartial<{weak:string;medium:string;strong:string;}>" + }, + "contrastThreshold": { + "type": "number" + }, + "error": { + "$ref": "#/definitions/DeepPartial<ThemeRichColor>" + }, + "gradients": { + "$ref": "#/definitions/DeepPartial<{brandVertical:string;brandHorizontal:string;}>" + }, + "hoverFactor": { + "type": "number" + }, + "info": { + "$ref": "#/definitions/DeepPartial<ThemeRichColor>" + }, + "mode": { + "enum": [ + "dark", + "light" + ], + "type": "string" + }, + "primary": { + "$ref": "#/definitions/DeepPartial<ThemeRichColor>" + }, + "secondary": { + "$ref": "#/definitions/DeepPartial<ThemeRichColor>" + }, + "success": { + "$ref": "#/definitions/DeepPartial<ThemeRichColor>" + }, + "text": { + "$ref": "#/definitions/DeepPartial<{primary:string;secondary:string;disabled:string;link:string;maxContrast:string;}>" + }, + "tonalOffset": { + "type": "number" + }, + "warning": { + "$ref": "#/definitions/DeepPartial<ThemeRichColor>" + } }, - "shades": { - "items": { - "$ref": "#/definitions/ThemeVizColor<\"red\">" - }, - "type": "array" - } - }, - "type": "object" + "type": "object" }, - { - "properties": { - "name": { - "const": "orange", - "type": "string" + "DeepPartial<ThemeRichColor>": { + "properties": { + "border": { + "description": "Used for borders", + "type": "string" + }, + "borderTransparent": { + "description": "Used for weak colored borders like larger alert/banner boxes and smaller badges and tags", + "type": "string" + }, + "contrastText": { + "description": "Text color for text ontop of main", + "type": "string" + }, + "main": { + "description": "Main color", + "type": "string" + }, + "name": { + "description": "color intent (primary, secondary, info, error, etc)", + "type": "string" + }, + "shade": { + "description": "Used for hover", + "type": "string" + }, + "text": { + "description": "Used for text", + "type": "string" + }, + "transparent": { + "description": "Used subtly colored backgrounds", + "type": "string" + } }, - "shades": { - "items": { - "$ref": "#/definitions/ThemeVizColor<\"orange\">" - }, - "type": "array" - } - }, - "type": "object" + "type": "object" }, - { - "properties": { - "name": { - "const": "yellow", - "type": "string" + "DeepPartial<{brandVertical:string;brandHorizontal:string;}>": { + "properties": { + "brandHorizontal": { + "type": "string" + }, + "brandVertical": { + "type": "string" + } }, - "shades": { - "items": { - "$ref": "#/definitions/ThemeVizColor<\"yellow\">" - }, - "type": "array" - } - }, - "type": "object" + "type": "object" }, - { - "properties": { - "name": { - "const": "green", - "type": "string" + "DeepPartial<{canvas:string;primary:string;secondary:string;elevated:string;}>": { + "properties": { + "canvas": { + "description": "Dashboard and body background", + "type": "string" + }, + "elevated": { + "description": "For popovers and menu backgrounds. This is the same color as primary in most light themes but in dark\nthemes it has a brighter shade to help give it contrast against the primary background.", + "type": "string" + }, + "primary": { + "description": "Primary content pane background (panels etc)", + "type": "string" + }, + "secondary": { + "description": "Cards and elements that need to stand out on the primary background", + "type": "string" + } }, - "shades": { - "items": { - "$ref": "#/definitions/ThemeVizColor<\"green\">" - }, - "type": "array" - } - }, - "type": "object" + "type": "object" }, - { - "properties": { - "name": { - "const": "blue", - "type": "string" + "DeepPartial<{primary:string;secondary:string;disabled:string;link:string;maxContrast:string;}>": { + "properties": { + "disabled": { + "type": "string" + }, + "link": { + "type": "string" + }, + "maxContrast": { + "description": "Used for auto white or dark text on colored backgrounds", + "type": "string" + }, + "primary": { + "type": "string" + }, + "secondary": { + "type": "string" + } }, - "shades": { - "items": { - "$ref": "#/definitions/ThemeVizColor<\"blue\">" - }, - "type": "array" - } - }, - "type": "object" + "type": "object" }, - { - "properties": { - "name": { - "const": "purple", - "type": "string" + "DeepPartial<{selected:string;selectedBorder:string;hover:string;hoverOpacity:number;focus:string;disabledBackground:string;disabledText:string;disabledOpacity:number;}>": { + "properties": { + "disabledBackground": { + "description": "Used for disabled buttons and inputs", + "type": "string" + }, + "disabledOpacity": { + "description": "Disablerd opacity", + "type": "number" + }, + "disabledText": { + "description": "Disabled text", + "type": "string" + }, + "focus": { + "description": "Used focused menu item / select option", + "type": "string" + }, + "hover": { + "description": "Used for hovered menu item / select option", + "type": "string" + }, + "hoverOpacity": { + "description": "Used for button/colored background hover opacity", + "type": "number" + }, + "selected": { + "description": "Used for selected menu item / select option", + "type": "string" + }, + "selectedBorder": { + "type": "string" + } }, - "shades": { - "items": { - "$ref": "#/definitions/ThemeVizColor<\"purple\">" - }, - "type": "array" - } - }, - "type": "object" - } - ] - } - }, - "properties": { - "colors": { - "$ref": "#/definitions/DeepPartial<ThemeColorsBase<ThemeRichColor>>" - }, - "name": { - "type": "string" - }, - "shape": { - "$ref": "#/definitions/ThemeShapeInput" - }, - "spacing": { - "properties": { - "gridSize": { - "type": "number" - } - }, - "type": "object" - }, - "typography": { - "$ref": "#/definitions/ThemeTypographyInput" - }, - "visualization": { - "properties": { - "hues": { - "items": { - "$ref": "#/definitions/ThemeVizHue" - }, - "type": "array" + "type": "object" }, - "palette": { - "items": { + "DeepPartial<{weak:string;medium:string;strong:string;}>": { + "properties": { + "medium": { + "type": "string" + }, + "strong": { + "type": "string" + }, + "weak": { + "type": "string" + } + }, + "type": "object" + }, + "ThemeShapeInput": { + "properties": { + "borderRadius": { + "type": "number" + } + }, + "type": "object" + }, + "ThemeTypographyInput": { + "properties": { + "fontFamily": { + "type": "string" + }, + "fontFamilyMonospace": { + "type": "string" + }, + "fontSize": { + "type": "number" + }, + "fontWeightBold": { + "type": "number" + }, + "fontWeightLight": { + "type": "number" + }, + "fontWeightMedium": { + "type": "number" + }, + "fontWeightRegular": { + "type": "number" + }, + "htmlFontSize": { + "type": "number" + } + }, + "type": "object" + }, + "ThemeVizColor<\"blue\">": { + "properties": { + "aliases": { + "items": { + "type": "string" + }, + "type": "array" + }, + "color": { + "type": "string" + }, + "name": { + "$ref": "#/definitions/ThemeVizColorShadeName_4" + }, + "primary": { + "type": "boolean" + } + }, + "type": "object" + }, + "ThemeVizColor<\"green\">": { + "properties": { + "aliases": { + "items": { + "type": "string" + }, + "type": "array" + }, + "color": { + "type": "string" + }, + "name": { + "$ref": "#/definitions/ThemeVizColorShadeName_3" + }, + "primary": { + "type": "boolean" + } + }, + "type": "object" + }, + "ThemeVizColor<\"orange\">": { + "properties": { + "aliases": { + "items": { + "type": "string" + }, + "type": "array" + }, + "color": { + "type": "string" + }, + "name": { + "$ref": "#/definitions/ThemeVizColorShadeName_1" + }, + "primary": { + "type": "boolean" + } + }, + "type": "object" + }, + "ThemeVizColor<\"purple\">": { + "properties": { + "aliases": { + "items": { + "type": "string" + }, + "type": "array" + }, + "color": { + "type": "string" + }, + "name": { + "$ref": "#/definitions/ThemeVizColorShadeName_5" + }, + "primary": { + "type": "boolean" + } + }, + "type": "object" + }, + "ThemeVizColor<\"red\">": { + "properties": { + "aliases": { + "items": { + "type": "string" + }, + "type": "array" + }, + "color": { + "type": "string" + }, + "name": { + "$ref": "#/definitions/ThemeVizColorShadeName" + }, + "primary": { + "type": "boolean" + } + }, + "type": "object" + }, + "ThemeVizColor<\"yellow\">": { + "properties": { + "aliases": { + "items": { + "type": "string" + }, + "type": "array" + }, + "color": { + "type": "string" + }, + "name": { + "$ref": "#/definitions/ThemeVizColorShadeName_2" + }, + "primary": { + "type": "boolean" + } + }, + "type": "object" + }, + "ThemeVizColorShadeName": { + "enum": [ + "dark-red", + "light-red", + "red", + "semi-dark-red", + "super-light-red" + ], "type": "string" - }, - "type": "array" + }, + "ThemeVizColorShadeName_1": { + "enum": [ + "dark-orange", + "light-orange", + "orange", + "semi-dark-orange", + "super-light-orange" + ], + "type": "string" + }, + "ThemeVizColorShadeName_2": { + "enum": [ + "dark-yellow", + "light-yellow", + "semi-dark-yellow", + "super-light-yellow", + "yellow" + ], + "type": "string" + }, + "ThemeVizColorShadeName_3": { + "enum": [ + "dark-green", + "green", + "light-green", + "semi-dark-green", + "super-light-green" + ], + "type": "string" + }, + "ThemeVizColorShadeName_4": { + "enum": [ + "blue", + "dark-blue", + "light-blue", + "semi-dark-blue", + "super-light-blue" + ], + "type": "string" + }, + "ThemeVizColorShadeName_5": { + "enum": [ + "dark-purple", + "light-purple", + "purple", + "semi-dark-purple", + "super-light-purple" + ], + "type": "string" + }, + "ThemeVizHue": { + "anyOf": [ + { + "properties": { + "name": { + "const": "red", + "type": "string" + }, + "shades": { + "items": { + "$ref": "#/definitions/ThemeVizColor<\"red\">" + }, + "type": "array" + } + }, + "type": "object" + }, + { + "properties": { + "name": { + "const": "orange", + "type": "string" + }, + "shades": { + "items": { + "$ref": "#/definitions/ThemeVizColor<\"orange\">" + }, + "type": "array" + } + }, + "type": "object" + }, + { + "properties": { + "name": { + "const": "yellow", + "type": "string" + }, + "shades": { + "items": { + "$ref": "#/definitions/ThemeVizColor<\"yellow\">" + }, + "type": "array" + } + }, + "type": "object" + }, + { + "properties": { + "name": { + "const": "green", + "type": "string" + }, + "shades": { + "items": { + "$ref": "#/definitions/ThemeVizColor<\"green\">" + }, + "type": "array" + } + }, + "type": "object" + }, + { + "properties": { + "name": { + "const": "blue", + "type": "string" + }, + "shades": { + "items": { + "$ref": "#/definitions/ThemeVizColor<\"blue\">" + }, + "type": "array" + } + }, + "type": "object" + }, + { + "properties": { + "name": { + "const": "purple", + "type": "string" + }, + "shades": { + "items": { + "$ref": "#/definitions/ThemeVizColor<\"purple\">" + }, + "type": "array" + } + }, + "type": "object" + } + ] } - }, - "type": "object" - } - }, - "type": "object" + }, + "properties": { + "colors": { + "$ref": "#/definitions/DeepPartial<ThemeColorsBase<ThemeRichColor>>" + }, + "name": { + "type": "string" + }, + "shape": { + "$ref": "#/definitions/ThemeShapeInput" + }, + "spacing": { + "properties": { + "gridSize": { + "type": "number" + } + }, + "type": "object" + }, + "typography": { + "$ref": "#/definitions/ThemeTypographyInput" + }, + "visualization": { + "properties": { + "hues": { + "items": { + "$ref": "#/definitions/ThemeVizHue" + }, + "type": "array" + }, + "palette": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + } + }, + "type": "object" } + From f1f0b8116daf938664b107509f6272230614358d Mon Sep 17 00:00:00 2001 From: Gareth <email@garethdawson.xyz> Date: Wed, 15 Oct 2025 18:46:59 +0800 Subject: [PATCH 27/75] Tempo: Add error source (#112358) * Tempo: Add error source to query data * use downstreamerrorf * update * streaming error source * fix --- pkg/tsdb/tempo/metrics_stream.go | 14 ++++++++++---- pkg/tsdb/tempo/search.go | 19 +++++++++++++++--- pkg/tsdb/tempo/search_stream.go | 9 ++++++--- pkg/tsdb/tempo/stream_handler.go | 19 +++++++++++------- pkg/tsdb/tempo/tempo.go | 8 +++++--- pkg/tsdb/tempo/trace.go | 33 ++++++++++++++++++++------------ pkg/tsdb/tempo/traceql_query.go | 27 +++++++++++++++++--------- 7 files changed, 88 insertions(+), 41 deletions(-) diff --git a/pkg/tsdb/tempo/metrics_stream.go b/pkg/tsdb/tempo/metrics_stream.go index cd711ac80f9..5cefc4c5e33 100644 --- a/pkg/tsdb/tempo/metrics_stream.go +++ b/pkg/tsdb/tempo/metrics_stream.go @@ -36,7 +36,7 @@ func (s *Service) runMetricsStream(ctx context.Context, req *backend.RunStreamRe response.Error = fmt.Errorf("error unmarshaling backend query model: %v", err) span.RecordError(response.Error) span.SetStatus(codes.Error, response.Error.Error()) - return err + return backend.DownstreamErrorf("error unmarshaling backend query model: %v", err) } tempoQuery := &PartialTempoQuery{} @@ -45,7 +45,7 @@ func (s *Service) runMetricsStream(ctx context.Context, req *backend.RunStreamRe response.Error = fmt.Errorf("error unmarshaling Tempo query model: %v", err) span.RecordError(response.Error) span.SetStatus(codes.Error, response.Error.Error()) - return err + return backend.DownstreamErrorf("failed to unmarshall Tempo query model: %w", err) } var qrr *tempopb.QueryRangeRequest @@ -54,11 +54,11 @@ func (s *Service) runMetricsStream(ctx context.Context, req *backend.RunStreamRe response.Error = fmt.Errorf("error unmarshaling Tempo query model: %v", err) span.RecordError(response.Error) span.SetStatus(codes.Error, response.Error.Error()) - return err + return backend.DownstreamErrorf("failed to unmarshall Tempo query model: %w", err) } if qrr.GetQuery() == "" { - return fmt.Errorf("query is empty") + return backend.DownstreamErrorf("tempo search query cannot be empty") } qrr.Start = uint64(backendQuery.TimeRange.From.UnixNano()) @@ -81,6 +81,9 @@ func (s *Service) runMetricsStream(ctx context.Context, req *backend.RunStreamRe span.RecordError(err) span.SetStatus(codes.Error, err.Error()) s.logger.Error("Error Search()", "err", err) + if backend.IsDownstreamHTTPError(err) { + return backend.DownstreamError(err) + } return err } @@ -92,6 +95,9 @@ func (s *Service) runMetricsStream(ctx context.Context, req *backend.RunStreamRe span.RecordError(err) span.SetStatus(codes.Error, err.Error()) s.logger.Error("Error Search()", "err", err) + if backend.IsDownstreamHTTPError(err) { + return backend.DownstreamError(err) + } return err } diff --git a/pkg/tsdb/tempo/search.go b/pkg/tsdb/tempo/search.go index 71cad92f08b..05d85d12c25 100644 --- a/pkg/tsdb/tempo/search.go +++ b/pkg/tsdb/tempo/search.go @@ -43,24 +43,27 @@ func (s *Service) Search(ctx context.Context, pCtx backend.PluginContext, query dsInfo, err := s.getDSInfo(ctx, pCtx) if err != nil { ctxLogger.Error("Failed to get datasource information", "error", err, "function", logEntrypoint()) - return nil, err + return nil, backend.DownstreamErrorf("failed to get datasource information: %w", err) } err = json.Unmarshal(query.JSON, model) if err != nil { ctxLogger.Error("Failed to unmarshall Tempo query model", "error", err, "function", logEntrypoint()) - return nil, err + return nil, backend.DownstreamErrorf("failed to unmarshall Tempo query model: %w", err) } req, err := createSearchRequest(ctx, dsInfo, model, query.TimeRange.From.Unix(), query.TimeRange.To.Unix()) if err != nil { ctxLogger.Error("Failed to create search request", "error", err, "function", logEntrypoint()) - return nil, err + return nil, backend.DownstreamErrorf("failed to create search request: %w", err) } resp, err := dsInfo.HTTPClient.Do(req) if err != nil { ctxLogger.Error("Failed to send request to Tempo", "error", err, "function", logEntrypoint()) + if backend.IsDownstreamHTTPError(err) { + return nil, backend.DownstreamError(err) + } return nil, err } @@ -78,6 +81,16 @@ func (s *Service) Search(ctx context.Context, pCtx backend.PluginContext, query return nil, err } + if resp.StatusCode != http.StatusOK { + ctxLogger.Error("Failed to execute search query", "error", err, "function", logEntrypoint()) + err := fmt.Errorf("failed to execute search query status: %s", resp.Status) + if backend.ErrorSourceFromHTTPStatus(resp.StatusCode) == backend.ErrorSourceDownstream { + return nil, backend.DownstreamError(err) + } + + return nil, err + } + var response tempopb.SearchResponse err = jsonpb.Unmarshal(bytes.NewReader(body), &response) diff --git a/pkg/tsdb/tempo/search_stream.go b/pkg/tsdb/tempo/search_stream.go index c78e138e4a7..f9394ea382b 100644 --- a/pkg/tsdb/tempo/search_stream.go +++ b/pkg/tsdb/tempo/search_stream.go @@ -43,7 +43,7 @@ func (s *Service) runSearchStream(ctx context.Context, req *backend.RunStreamReq response.Error = fmt.Errorf("error unmarshaling backend query model: %v", err) span.RecordError(response.Error) span.SetStatus(codes.Error, response.Error.Error()) - return err + return backend.DownstreamErrorf("error unmarshaling backend query model: %v", err) } var sr *tempopb.SearchRequest @@ -52,11 +52,11 @@ func (s *Service) runSearchStream(ctx context.Context, req *backend.RunStreamReq response.Error = fmt.Errorf("error unmarshaling Tempo query model: %v", err) span.RecordError(response.Error) span.SetStatus(codes.Error, response.Error.Error()) - return err + return backend.DownstreamErrorf("failed to unmarshall Tempo query model: %w", err) } if sr.GetQuery() == "" { - return fmt.Errorf("query is empty") + return backend.DownstreamErrorf("tempo search query cannot be empty") } sr.Start = uint32(backendQuery.TimeRange.From.Unix()) @@ -72,6 +72,9 @@ func (s *Service) runSearchStream(ctx context.Context, req *backend.RunStreamReq span.RecordError(err) span.SetStatus(codes.Error, err.Error()) s.logger.Error("Error Search()", "err", err) + if backend.IsDownstreamHTTPError(err) { + return backend.DownstreamError(err) + } return err } diff --git a/pkg/tsdb/tempo/stream_handler.go b/pkg/tsdb/tempo/stream_handler.go index 6cae17206c4..d3d4a8d7e74 100644 --- a/pkg/tsdb/tempo/stream_handler.go +++ b/pkg/tsdb/tempo/stream_handler.go @@ -10,17 +10,22 @@ import ( func (s *Service) SubscribeStream(_ context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) { s.logger.Debug("Allowing access to stream", "path", req.Path, "user", req.PluginContext.User) - status := backend.SubscribeStreamStatusPermissionDenied + if strings.HasPrefix(req.Path, SearchPathPrefix) { - status = backend.SubscribeStreamStatusOK + return &backend.SubscribeStreamResponse{ + Status: backend.SubscribeStreamStatusOK, + }, nil } + if strings.HasPrefix(req.Path, MetricsPathPrefix) { - status = backend.SubscribeStreamStatusOK + return &backend.SubscribeStreamResponse{ + Status: backend.SubscribeStreamStatusOK, + }, nil } return &backend.SubscribeStreamResponse{ - Status: status, - }, nil + Status: backend.SubscribeStreamStatusPermissionDenied, + }, backend.DownstreamErrorf("stream path not supported: %s", req.Path) } func (s *Service) PublishStream(_ context.Context, _ *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) { @@ -38,7 +43,7 @@ func (s *Service) RunStream(ctx context.Context, request *backend.RunStreamReque if strings.HasPrefix(request.Path, SearchPathPrefix) { if err != nil { - return err + return backend.DownstreamErrorf("failed to get datasource information: %w", err) } if err = s.runSearchStream(ctx, request, sender, tempoDatasource); err != nil { return sendError(err, sender) @@ -48,7 +53,7 @@ func (s *Service) RunStream(ctx context.Context, request *backend.RunStreamReque } if strings.HasPrefix(request.Path, MetricsPathPrefix) { if err != nil { - return err + return backend.DownstreamErrorf("failed to get datasource information: %w", err) } if err = s.runMetricsStream(ctx, request, sender, tempoDatasource); err != nil { return sendError(err, sender) diff --git a/pkg/tsdb/tempo/tempo.go b/pkg/tsdb/tempo/tempo.go index ccb6173489b..a7c8fdf1ee6 100644 --- a/pkg/tsdb/tempo/tempo.go +++ b/pkg/tsdb/tempo/tempo.go @@ -67,7 +67,7 @@ func newInstanceSettings(httpClientProvider *httpclient.Provider) datasource.Ins opts, err := settings.HTTPClientOptions(ctx) if err != nil { ctxLogger.Error("Failed to get HTTP client options", "error", err, "function", logEntrypoint()) - return nil, err + return nil, backend.DownstreamErrorf("error reading settings: %w", err) } opts.ForwardHTTPHeaders = true @@ -112,7 +112,8 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) res, err = s.getTrace(ctx, req.PluginContext, q) if err != nil { ctxLogger.Error("Error processing TraceId query", "error", err) - return response, err + response.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(err) + continue } case string(dataquery.TempoQueryTypeTraceqlSearch): @@ -121,7 +122,8 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) res, err = s.runTraceQlQuery(ctx, req.PluginContext, q) if err != nil { ctxLogger.Error("Error processing TraceQL query", "error", err) - return response, err + response.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(err) + continue } default: diff --git a/pkg/tsdb/tempo/trace.go b/pkg/tsdb/tempo/trace.go index 6cc705de502..ec3882ee810 100644 --- a/pkg/tsdb/tempo/trace.go +++ b/pkg/tsdb/tempo/trace.go @@ -35,19 +35,19 @@ func (s *Service) getTrace(ctx context.Context, pCtx backend.PluginContext, quer err := json.Unmarshal(query.JSON, model) if err != nil { ctxLogger.Error("Failed to unmarshall Tempo query model", "error", err, "function", logEntrypoint()) - return result, err + return result, backend.DownstreamErrorf("failed to unmarshall Tempo query model: %w", err) } dsInfo, err := s.getDSInfo(ctx, pCtx) if err != nil { ctxLogger.Error("Failed to get datasource information", "error", err, "function", logEntrypoint()) - return nil, err + return nil, backend.DownstreamErrorf("failed to get datasource information: %w", err) } if model.Query == nil || *model.Query == "" { err := fmt.Errorf("trace id is required") ctxLogger.Error("Failed to validate model query", "error", err, "function", logEntrypoint()) - return result, err + return result, backend.DownstreamErrorf("failed to validate model query: %w", err) } var apiVersion = TraceRequestApiVersionV2 @@ -69,10 +69,16 @@ func (s *Service) getTrace(ctx context.Context, pCtx backend.PluginContext, quer if resp.StatusCode != http.StatusOK { ctxLogger.Error("Failed to get trace", "error", err, "function", logEntrypoint()) - result.Error = fmt.Errorf("failed to get trace with id: %s Status: %s Body: %s", *model.Query, resp.Status, string(traceBody)) - span.RecordError(result.Error) - span.SetStatus(codes.Error, result.Error.Error()) - return result, nil + err := fmt.Errorf("failed to get trace with id: %s Status: %s Body: %s", *model.Query, resp.Status, string(traceBody)) + + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + + if backend.ErrorSourceFromHTTPStatus(resp.StatusCode) == backend.ErrorSourceDownstream { + return nil, backend.DownstreamError(err) + } + + return nil, err } var frame *data.Frame @@ -116,10 +122,10 @@ func (s *Service) getTrace(ctx context.Context, pCtx backend.PluginContext, quer if frame == nil { result.Status = http.StatusNotFound - result.Error = fmt.Errorf("failed to get trace with id: %s Status: %s", *model.Query, result.Status) - span.RecordError(result.Error) - span.SetStatus(codes.Error, result.Error.Error()) - return result, nil + err := fmt.Errorf("failed to get trace with id: %s Status: %s", *model.Query, result.Status) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return nil, backend.DownstreamError(err) } frame.Meta.Custom = map[string]interface{}{ @@ -143,7 +149,7 @@ func (s *Service) performTraceRequest(ctx context.Context, dsInfo *DatasourceInf ctxLogger.Error("Failed to create request", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, nil, err + return nil, nil, backend.DownstreamErrorf("failed to create request: %w", err) } resp, err := dsInfo.HTTPClient.Do(request) @@ -151,6 +157,9 @@ func (s *Service) performTraceRequest(ctx context.Context, dsInfo *DatasourceInf ctxLogger.Error("Failed to send request to Tempo", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) + if backend.IsDownstreamHTTPError(err) { + return nil, nil, backend.DownstreamError(err) + } return nil, nil, fmt.Errorf("failed get to tempo: %w", err) } diff --git a/pkg/tsdb/tempo/traceql_query.go b/pkg/tsdb/tempo/traceql_query.go index babd269e7ac..3739902efb3 100644 --- a/pkg/tsdb/tempo/traceql_query.go +++ b/pkg/tsdb/tempo/traceql_query.go @@ -32,7 +32,7 @@ func (s *Service) runTraceQlQuery(ctx context.Context, pCtx backend.PluginContex err := json.Unmarshal(backendQuery.JSON, tempoQuery) if err != nil { ctxLogger.Error("Failed to unmarshall Tempo query model", "error", err, "function", logEntrypoint()) - return nil, err + return nil, backend.DownstreamErrorf("failed to unmarshall Tempo query model: %w", err) } if isMetricsQuery(*tempoQuery.Query) { @@ -60,13 +60,13 @@ func (s *Service) runTraceQlQueryMetrics(ctx context.Context, pCtx backend.Plugi dsInfo, err := s.getDSInfo(ctx, pCtx) if err != nil { ctxLogger.Error("Failed to get datasource information", "error", err, "function", logEntrypoint()) - return nil, err + return nil, backend.DownstreamErrorf("failed to get datasource information: %w", err) } if tempoQuery.Query == nil || *tempoQuery.Query == "" { err := fmt.Errorf("query is required") ctxLogger.Error("Failed to validate model query", "error", err, "function", logEntrypoint()) - return result, err + return result, backend.DownstreamErrorf("failed to validate model query: %w", err) } resp, responseBody, err := s.performMetricsQuery(ctx, dsInfo, tempoQuery, backendQuery, span) @@ -83,10 +83,16 @@ func (s *Service) runTraceQlQueryMetrics(ctx context.Context, pCtx backend.Plugi if resp.StatusCode != http.StatusOK { ctxLogger.Error("Failed to execute TraceQL query", "error", err, "function", logEntrypoint()) - result.Error = fmt.Errorf("failed to execute TraceQL query: %s Status: %s Body: %s", *tempoQuery.Query, resp.Status, string(responseBody)) - span.RecordError(result.Error) - span.SetStatus(codes.Error, result.Error.Error()) - return result, nil + err := fmt.Errorf("failed to execute TraceQL query: %s Status: %s Body: %s", *tempoQuery.Query, resp.Status, string(responseBody)) + + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + + if backend.ErrorSourceFromHTTPStatus(resp.StatusCode) == backend.ErrorSourceDownstream { + err = backend.DownstreamError(err) + } + + return nil, err } if isInstantQuery(tempoQuery.MetricsQueryType) { @@ -137,7 +143,7 @@ func (s *Service) performMetricsQuery(ctx context.Context, dsInfo *DatasourceInf ctxLogger.Error("Failed to create request", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, nil, err + return nil, nil, backend.DownstreamErrorf("failed to create request: %w", err) } resp, err := dsInfo.HTTPClient.Do(request) @@ -145,7 +151,10 @@ func (s *Service) performMetricsQuery(ctx context.Context, dsInfo *DatasourceInf ctxLogger.Error("Failed to send request to Tempo", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, nil, fmt.Errorf("failed get to tempo: %w", err) + if backend.IsDownstreamHTTPError(err) { + return nil, nil, backend.DownstreamError(err) + } + return nil, nil, fmt.Errorf("failed to send request to Tempo: %w", err) } body, err := io.ReadAll(resp.Body) From a3c4f5cb9feec4b978c01d80be4469a8eae3f1e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20=C5=A0tibran=C3=BD?= <pstibrany@gmail.com> Date: Wed, 15 Oct 2025 13:17:44 +0200 Subject: [PATCH 28/75] Fix flaky TestConcurrentIndexUpdateAndSearchWithIndexMinUpdateInterval test (#112419) Collect different RVs from update, and check that difference between them is at least 0.9*minInterval. --- pkg/storage/unified/search/bleve_test.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pkg/storage/unified/search/bleve_test.go b/pkg/storage/unified/search/bleve_test.go index 748609aa513..e34712f934d 100644 --- a/pkg/storage/unified/search/bleve_test.go +++ b/pkg/storage/unified/search/bleve_test.go @@ -1469,13 +1469,13 @@ func TestConcurrentIndexUpdateAndSearchWithIndexMinUpdateInterval(t *testing.T) attemptedUpdates := atomic.NewInt64(0) // Verify that each returned RV (unix timestamp in millis) is either the same as before, or at least minInterval later. - const searchConcurrency = 25 + const searchConcurrency = 10 for i := 0; i < searchConcurrency; i++ { wg.Add(1) go func() { defer wg.Done() - prevRV := int64(0) + var collectedRVs []int64 for ctx.Err() == nil { attemptedUpdates.Inc() @@ -1483,20 +1483,19 @@ func TestConcurrentIndexUpdateAndSearchWithIndexMinUpdateInterval(t *testing.T) rv, err := idx.UpdateIndex(t.Context()) require.NoError(t, err) - // Our update function returns unix timestamp in millis. We expect it to not change at all, or change by minInterval. - if prevRV > 0 { - rvDiff := rv - prevRV - if rvDiff == 0 { - // OK - } else { - // Allow returned RV to be within 20% of minInterval (to account for slow CI machines). - require.InDelta(t, minInterval.Milliseconds(), rvDiff, float64(minInterval.Milliseconds())*0.20) - } + if len(collectedRVs) == 0 || collectedRVs[len(collectedRVs)-1] != rv { + collectedRVs = append(collectedRVs, rv) } - prevRV = rv require.Equal(t, int64(10), searchTitle(t, idx, "Document", 10, ns).TotalHits) } + + t.Log(collectedRVs) + for i := 1; i < len(collectedRVs); i++ { + // We allow next RV to be 0.9*minInterval later, to account for possible clock skew between time measurements. + // (We get measurements from update function, but check is done on times inside updater) + require.GreaterOrEqual(t, collectedRVs[i], collectedRVs[i-1]+(9*int64(minInterval/time.Millisecond)/10)) + } }() } From 89b3f64479e7b3d253b265367eecdf74034e26a2 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:20:10 +0200 Subject: [PATCH 29/75] ExtensionSidebar: Introduce ToggleExtensionSidebarEvent (#112325) * ExtensionSidebar: Use appEvents for opening/closing sidebar * Refactor * Fix lint * Introduce ToggleExtensionSidebar * Revert keyboard shortcuts * Revert "Refactor" This reverts commit ffd901ed0c9db91f611c880cea79ff6c3910c392. * Revert "ExtensionSidebar: Use appEvents for opening/closing sidebar" This reverts commit 8083bec978fbdc1c27cdf69ae855b5d4d0d33920. * Fix test * Refactor to prevent flakiness * Try again * Improve mocks * simple * trying anything at this point * Flip test order * Revert "Flip test order" This reverts commit 2dd33f744e6b95b540c8f7297901e920f3ce18e4. * Update togglespyes and act * Try async await * Update test * Update solution * Update * Test * Update * Refactor * Uodate comments * Types * Fix type --- .../src/types/pluginExtensions.ts | 9 ++ .../ExtensionSidebarProvider.test.tsx | 147 +++++++++++++++++- .../ExtensionSidebarProvider.tsx | 19 ++- .../app/features/plugins/extensions/utils.tsx | 16 +- public/app/types/events.ts | 10 ++ 5 files changed, 191 insertions(+), 10 deletions(-) diff --git a/packages/grafana-data/src/types/pluginExtensions.ts b/packages/grafana-data/src/types/pluginExtensions.ts index 9a02e5fac27..47e130afcce 100644 --- a/packages/grafana-data/src/types/pluginExtensions.ts +++ b/packages/grafana-data/src/types/pluginExtensions.ts @@ -181,6 +181,15 @@ export type PluginExtensionEventHelpers<Context extends object = object> = { * Closes the extension sidebar. */ closeSidebar: () => void; + /** + * @internal + * Toggles the extension sidebar with the registered component. + * If the sidebar is open with the same component, it will be closed. + * If the sidebar is closed or open with a different component, it will be opened with the specified component. + * @param componentTitle The title of the component to be toggled in the sidebar. + * @param props The props to be passed to the component. + */ + toggleSidebar: (componentTitle: string, props?: Record<string, unknown>) => void; }; // Extension Points & Contexts diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx index 955c85923b6..c6c8ee37239 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.test.tsx @@ -1,9 +1,9 @@ import { render, screen, act } from '@testing-library/react'; -import { store, EventBusSrv, EventBus } from '@grafana/data'; +import { store, EventBusSrv, EventBus, ExtensionInfo } from '@grafana/data'; import { getAppEvents, setAppEvents, locationService } from '@grafana/runtime'; import { getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils'; -import { OpenExtensionSidebarEvent, CloseExtensionSidebarEvent } from 'app/types/events'; +import { OpenExtensionSidebarEvent, CloseExtensionSidebarEvent, ToggleExtensionSidebarEvent } from 'app/types/events'; import { ExtensionSidebarContextProvider, @@ -17,11 +17,17 @@ const mockComponent = { title: 'Test Component', description: 'Test Description', targets: [], -}; +} as ExtensionInfo; + +const mockDifferentComponent = { + title: 'Different Component', + description: 'Different Description', + targets: [], +} as ExtensionInfo; const mockPluginMeta = { pluginId: 'grafana-investigations-app', - addedComponents: [mockComponent], + addedComponents: [mockComponent, mockDifferentComponent], addedLinks: [], }; @@ -54,6 +60,7 @@ jest.mock('@grafana/runtime', () => ({ title: mockComponent.title, }, ], + isLoading: false, })), })); @@ -334,8 +341,133 @@ describe('ExtensionSidebarProvider', () => { expect(screen.getByTestId('docked-component-id')).toHaveTextContent('undefined'); }); - it('should unsubscribe from both OpenExtensionSidebarEvent and CloseExtensionSidebarEvent on unmount', () => { - const unsubscribeMocks = [jest.fn(), jest.fn()]; + it('should subscribe to ToggleExtensionSidebarEvent', () => { + render( + <ExtensionSidebarContextProvider> + <TestComponent /> + </ExtensionSidebarContextProvider> + ); + + expect(subscribeSpy).toHaveBeenCalledWith(ToggleExtensionSidebarEvent, expect.any(Function)); + }); + + it('should toggle sidebar when receiving ToggleExtensionSidebarEvent', () => { + const TestComponentWithProps = () => { + const context = useExtensionSidebarContext(); + return ( + <div> + <div data-testid="is-open">{context.isOpen.toString()}</div> + <div data-testid="docked-component-id">{context.dockedComponentId || 'undefined'}</div> + <div data-testid="props">{context.props ? JSON.stringify(context.props) : 'undefined'}</div> + </div> + ); + }; + + render( + <ExtensionSidebarContextProvider> + <TestComponentWithProps /> + </ExtensionSidebarContextProvider> + ); + + // Sidebar is closed + expect(screen.getByTestId('is-open')).toHaveTextContent('false'); + + // Toggle the sidebar to open it + act(() => { + // Call the toggle event handler + const toggleEventSubscriberCall = subscribeSpy.mock.calls.find((call) => call[0] === ToggleExtensionSidebarEvent); + expect(toggleEventSubscriberCall).toBeDefined(); + const [, subscriberFn] = toggleEventSubscriberCall!; + + subscriberFn( + new ToggleExtensionSidebarEvent({ + pluginId: 'grafana-investigations-app', + componentTitle: 'Test Component', + props: { testProp: 'test value' }, + }) + ); + }); + + // Sidebar is open + expect(screen.getByTestId('is-open')).toHaveTextContent('true'); + expect(screen.getByTestId('props')).toHaveTextContent('{"testProp":"test value"}'); + const expectedComponentId = JSON.stringify({ + pluginId: 'grafana-investigations-app', + componentTitle: 'Test Component', + }); + expect(screen.getByTestId('docked-component-id')).toHaveTextContent(expectedComponentId); + + // Toggle the sidebar to close it + act(() => { + // Call the toggle event handler + const toggleEventSubscriberCall = subscribeSpy.mock.calls + .slice() + .reverse() + .find((call) => call[0] === ToggleExtensionSidebarEvent); + expect(toggleEventSubscriberCall).toBeDefined(); + const [, subscriberFn] = toggleEventSubscriberCall!; + + subscriberFn( + new ToggleExtensionSidebarEvent({ + pluginId: mockPluginMeta.pluginId, + componentTitle: mockComponent.title, + }) + ); + }); + + expect(screen.getByTestId('is-open')).toHaveTextContent('false'); + expect(screen.getByTestId('docked-component-id')).toHaveTextContent('undefined'); + }); + + it('should toggle to different component when receiving ToggleExtensionSidebarEvent for different component', () => { + const componentId = getComponentIdFromComponentMeta(mockPluginMeta.pluginId, mockComponent.title); + (store.get as jest.Mock).mockReturnValue(componentId); + + const TestComponentWithProps = () => { + const context = useExtensionSidebarContext(); + return ( + <div> + <div data-testid="is-open">{context.isOpen.toString()}</div> + <div data-testid="docked-component-id">{context.dockedComponentId || 'undefined'}</div> + <button onClick={() => context.setDockedComponentId(componentId)}>Open Sidebar</button> + </div> + ); + }; + + render( + <ExtensionSidebarContextProvider> + <TestComponentWithProps /> + </ExtensionSidebarContextProvider> + ); + + act(() => { + // Find the ToggleExtensionSidebarEvent subscriber + const toggleEventSubscriberCall = subscribeSpy.mock.calls + .slice() + .reverse() + .find((call) => call[0] === ToggleExtensionSidebarEvent); + expect(toggleEventSubscriberCall).toBeDefined(); + const [, subscriberFn] = toggleEventSubscriberCall!; + + // Call the toggle event handler with a different component + subscriberFn( + new ToggleExtensionSidebarEvent({ + pluginId: mockPluginMeta.pluginId, + componentTitle: 'Different Component', + }) + ); + }); + + expect(screen.getByTestId('is-open')).toHaveTextContent('true'); + const expectedComponentId = JSON.stringify({ + pluginId: mockPluginMeta.pluginId, + componentTitle: 'Different Component', + }); + expect(screen.getByTestId('docked-component-id')).toHaveTextContent(expectedComponentId); + }); + + it('should unsubscribe from all event subscriptions on unmount', () => { + const unsubscribeMocks = [jest.fn(), jest.fn(), jest.fn()]; let callIndex = 0; subscribeSpy.mockImplementation(() => ({ @@ -350,9 +482,10 @@ describe('ExtensionSidebarProvider', () => { unmount(); - // Both event subscriptions should be unsubscribed + // All event subscriptions should be unsubscribed expect(unsubscribeMocks[0]).toHaveBeenCalled(); expect(unsubscribeMocks[1]).toHaveBeenCalled(); + expect(unsubscribeMocks[2]).toHaveBeenCalled(); }); it('should subscribe to location service observable', () => { diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx index 9dcb1cf1edb..89073d6779a 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx @@ -4,7 +4,7 @@ import { useLocalStorage } from 'react-use'; import { PluginExtensionPoints, store } from '@grafana/data'; import { getAppEvents, reportInteraction, usePluginLinks, locationService } from '@grafana/runtime'; import { ExtensionPointPluginMeta, getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils'; -import { CloseExtensionSidebarEvent, OpenExtensionSidebarEvent } from 'app/types/events'; +import { CloseExtensionSidebarEvent, OpenExtensionSidebarEvent, ToggleExtensionSidebarEvent } from 'app/types/events'; import { DEFAULT_EXTENSION_SIDEBAR_WIDTH, MAX_EXTENSION_SIDEBAR_WIDTH } from './ExtensionSidebar'; @@ -174,13 +174,28 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo setDockedComponentId(undefined); }; + const toggleSidebarHandler = (event: ToggleExtensionSidebarEvent) => { + const currentComponentMeta = getComponentMetaFromComponentId(dockedComponentId ?? ''); + const isCurrentlyOpen = + currentComponentMeta?.pluginId === event.payload.pluginId && + currentComponentMeta?.componentTitle === event.payload.componentTitle; + + if (isCurrentlyOpen) { + closeSidebarHandler(); + } else { + openSidebarHandler(event); + } + }; + const openSubscription = getAppEvents().subscribe(OpenExtensionSidebarEvent, openSidebarHandler); const closeSubscription = getAppEvents().subscribe(CloseExtensionSidebarEvent, closeSidebarHandler); + const toggleSubscription = getAppEvents().subscribe(ToggleExtensionSidebarEvent, toggleSidebarHandler); return () => { openSubscription.unsubscribe(); closeSubscription.unsubscribe(); + toggleSubscription.unsubscribe(); }; - }, [setDockedComponentWithProps, availableComponents]); + }, [setDockedComponentWithProps, availableComponents, dockedComponentId]); // update the stored docked component id when it changes useEffect(() => { diff --git a/public/app/features/plugins/extensions/utils.tsx b/public/app/features/plugins/extensions/utils.tsx index ee064c0a387..a10460d786c 100644 --- a/public/app/features/plugins/extensions/utils.tsx +++ b/public/app/features/plugins/extensions/utils.tsx @@ -20,7 +20,12 @@ import { reportInteraction, config, AppPluginConfig } from '@grafana/runtime'; import { Modal } from '@grafana/ui'; import appEvents from 'app/core/app_events'; import { getPluginSettings } from 'app/features/plugins/pluginSettings'; -import { CloseExtensionSidebarEvent, OpenExtensionSidebarEvent, ShowModalReactEvent } from 'app/types/events'; +import { + CloseExtensionSidebarEvent, + OpenExtensionSidebarEvent, + ShowModalReactEvent, + ToggleExtensionSidebarEvent, +} from 'app/types/events'; import { RestrictedGrafanaApisProvider } from '../components/restrictedGrafanaApis/RestrictedGrafanaApisProvider'; @@ -550,6 +555,15 @@ export function getLinkExtensionOnClick( closeSidebar: () => { appEvents.publish(new CloseExtensionSidebarEvent()); }, + toggleSidebar: (componentTitle, context) => { + appEvents.publish( + new ToggleExtensionSidebarEvent({ + props: context, + pluginId, + componentTitle, + }) + ); + }, }; log.debug(`onClick '${config.title}' at '${extensionPointId}'`); diff --git a/public/app/types/events.ts b/public/app/types/events.ts index 978dfbf811f..5c7e4ba151b 100644 --- a/public/app/types/events.ts +++ b/public/app/types/events.ts @@ -34,6 +34,12 @@ export interface OpenExtensionSidebarPayload { componentTitle: string; } +export interface ToggleExtensionSidebarPayload { + props?: Record<string, unknown>; + pluginId: string; + componentTitle: string; +} + export interface ShowConfirmModalPayload { title?: string; text?: string; @@ -181,6 +187,10 @@ export class CloseExtensionSidebarEvent extends BusEventBase { static type = 'close-extension-sidebar'; } +export class ToggleExtensionSidebarEvent extends BusEventWithPayload<ToggleExtensionSidebarPayload> { + static type = 'toggle-extension-sidebar'; +} + /** * @deprecated use ShowModalReactEvent instead that has this capability built in */ From b7a6e1ca530f9d3cd3972b59a5e2dd6e8eab6024 Mon Sep 17 00:00:00 2001 From: Juan Cabanas <juan.cabanas@grafana.com> Date: Wed, 15 Oct 2025 09:05:02 -0300 Subject: [PATCH 30/75] Dashboard Library: Display datasource plugin dashboards in empty page (#111279) --- pkg/services/dashboardimport/api/api.go | 50 ++- pkg/services/dashboardimport/api/api_test.go | 83 ++++- .../dashboardimport/dashboardimport.go | 1 + .../dashboardimport/service/service.go | 13 +- pkg/services/featuremgmt/registry.go | 2 +- pkg/services/featuremgmt/toggles_gen.csv | 2 +- pkg/services/featuremgmt/toggles_gen.json | 10 +- public/api-enterprise-spec.json | 4 + public/api-merged.json | 30 ++ .../pages/DashboardScenePageStateManager.ts | 63 +++- .../saving/DashboardPrompt.test.tsx | 335 ++++++++++++++++-- .../saving/DashboardPrompt.tsx | 46 ++- .../dashboard-scene/utils/interactions.ts | 13 +- .../dashboard-scene/utils/tracking.ts | 22 +- .../containers/DashboardPageProxy.tsx | 4 +- .../containers/NewDashboardWithDS.tsx | 2 +- .../DashboardEmpty/DashboardEmpty.test.tsx | 48 ++- .../DashboardEmpty/DashboardEmpty.tsx | 3 + .../DashboardLibrarySection.tsx | 199 +++++++++++ .../dashgrid/DashboardLibrary/interactions.ts | 35 ++ .../app/features/dashboard/dashgrid/types.ts | 3 + public/app/routes/routes.tsx | 10 + public/app/types/dashboard.ts | 1 + .../dashboard-library/dashboard_library_1.jpg | Bin 0 -> 103917 bytes .../dashboard-library/dashboard_library_2.jpg | Bin 0 -> 113221 bytes .../dashboard-library/dashboard_library_3.jpg | Bin 0 -> 112953 bytes .../dashboard-library/dashboard_library_4.jpg | Bin 0 -> 92639 bytes .../dashboard-library/dashboard_library_5.jpg | Bin 0 -> 84785 bytes .../dashboard-library/dashboard_library_6.jpg | Bin 0 -> 111876 bytes public/locales/en-US/grafana.json | 6 +- public/openapi3.json | 34 ++ 31 files changed, 972 insertions(+), 47 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/DashboardLibrary/DashboardLibrarySection.tsx create mode 100644 public/app/features/dashboard/dashgrid/DashboardLibrary/interactions.ts create mode 100644 public/app/features/dashboard/dashgrid/types.ts create mode 100644 public/img/dashboard-library/dashboard_library_1.jpg create mode 100644 public/img/dashboard-library/dashboard_library_2.jpg create mode 100644 public/img/dashboard-library/dashboard_library_3.jpg create mode 100644 public/img/dashboard-library/dashboard_library_4.jpg create mode 100644 public/img/dashboard-library/dashboard_library_5.jpg create mode 100644 public/img/dashboard-library/dashboard_library_6.jpg diff --git a/pkg/services/dashboardimport/api/api.go b/pkg/services/dashboardimport/api/api.go index 8134b190de9..a566047c2b5 100644 --- a/pkg/services/dashboardimport/api/api.go +++ b/pkg/services/dashboardimport/api/api.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboardimport" "github.com/grafana/grafana/pkg/services/dashboardimport/utils" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/web" @@ -23,15 +24,17 @@ type ImportDashboardAPI struct { quotaService QuotaService pluginStore pluginstore.Store ac accesscontrol.AccessControl + features featuremgmt.FeatureToggles } func New(dashboardImportService dashboardimport.Service, quotaService QuotaService, - pluginStore pluginstore.Store, ac accesscontrol.AccessControl) *ImportDashboardAPI { + pluginStore pluginstore.Store, ac accesscontrol.AccessControl, features featuremgmt.FeatureToggles) *ImportDashboardAPI { return &ImportDashboardAPI{ dashboardImportService: dashboardImportService, quotaService: quotaService, pluginStore: pluginStore, ac: ac, + features: features, } } @@ -43,9 +46,48 @@ func (api *ImportDashboardAPI) RegisterAPIEndpoints(routeRegister routing.RouteR authorize(accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate)), routing.Wrap(api.ImportDashboard), ) + if api.features.IsEnabledGlobally(featuremgmt.FlagDashboardLibrary) { + route.Post( + "/interpolate", + authorize(accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate)), + routing.Wrap(api.InterpolateDashboard), + ) + } }, middleware.ReqSignedIn) } +// swagger:route POST /dashboards/interpolate dashboards interpolateDashboard +// +// Interpolate dashboard. This is an experimental endpoint under dashboardLibrary FF and is subject to change. +// +// Responses: +// 200: interpolateDashboardResponse +// 400: badRequestError +// 401: unauthorisedError +// 422: unprocessableEntityError +// 500: internalServerError +func (api *ImportDashboardAPI) InterpolateDashboard(c *contextmodel.ReqContext) response.Response { + req := dashboardimport.ImportDashboardRequest{} + if err := web.Bind(c.Req, &req); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } + + if req.PluginId == "" { + return response.Error(http.StatusUnprocessableEntity, "pluginId must be set", nil) + } + + resp, err := api.dashboardImportService.InterpolateDashboard(c.Req.Context(), &req) + if err != nil { + return response.Error(http.StatusInternalServerError, "failed to interpolate dashboard", err) + } + + resp.Del("__elements") + resp.Del("__inputs") + resp.Del("__requires") + + return response.JSON(http.StatusOK, resp) +} + // swagger:route POST /dashboards/import dashboards importDashboard // // Import dashboard. @@ -110,3 +152,9 @@ type ImportDashboardResponse struct { // in: body Body dashboardimport.ImportDashboardResponse `json:"body"` } + +// swagger:response interpolateDashboardResponse +type InterpolateDashboardResponse struct { + // in: body + Body interface{} `json:"body"` +} diff --git a/pkg/services/dashboardimport/api/api_test.go b/pkg/services/dashboardimport/api/api_test.go index 84a5636575e..17e0e5d1685 100644 --- a/pkg/services/dashboardimport/api/api_test.go +++ b/pkg/services/dashboardimport/api/api_test.go @@ -15,6 +15,7 @@ import ( contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/dashboardimport" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/web/webtest" @@ -30,7 +31,7 @@ func TestImportDashboardAPI(t *testing.T) { }, } - importDashboardAPI := New(service, quotaServiceFunc(quotaNotReached), nil, actest.FakeAccessControl{ExpectedEvaluate: true}) + importDashboardAPI := New(service, quotaServiceFunc(quotaNotReached), nil, actest.FakeAccessControl{ExpectedEvaluate: true}, featuremgmt.WithFeatures()) routeRegister := routing.NewRouteRegister() importDashboardAPI.RegisterAPIEndpoints(routeRegister) s := webtest.NewServer(t, routeRegister) @@ -107,7 +108,7 @@ func TestImportDashboardAPI(t *testing.T) { }, } - importDashboardAPI := New(service, quotaServiceFunc(quotaNotReached), nil, actest.FakeAccessControl{ExpectedEvaluate: true}) + importDashboardAPI := New(service, quotaServiceFunc(quotaNotReached), nil, actest.FakeAccessControl{ExpectedEvaluate: true}, featuremgmt.WithFeatures()) routeRegister := routing.NewRouteRegister() importDashboardAPI.RegisterAPIEndpoints(routeRegister) s := webtest.NewServer(t, routeRegister) @@ -135,7 +136,7 @@ func TestImportDashboardAPI(t *testing.T) { t.Run("Quota reached", func(t *testing.T) { service := &serviceMock{} - importDashboardAPI := New(service, quotaServiceFunc(quotaReached), nil, actest.FakeAccessControl{ExpectedEvaluate: true}) + importDashboardAPI := New(service, quotaServiceFunc(quotaReached), nil, actest.FakeAccessControl{ExpectedEvaluate: true}, featuremgmt.WithFeatures()) routeRegister := routing.NewRouteRegister() importDashboardAPI.RegisterAPIEndpoints(routeRegister) @@ -159,8 +160,74 @@ func TestImportDashboardAPI(t *testing.T) { }) } +func TestInterpolateDashboardFeatureFlag(t *testing.T) { + t.Run("Feature flag disabled - interpolate endpoint should return 404", func(t *testing.T) { + service := &serviceMock{} + // Create features with dashboardLibrary disabled + features := featuremgmt.WithFeatures() + importDashboardAPI := New(service, quotaServiceFunc(quotaNotReached), nil, actest.FakeAccessControl{ExpectedEvaluate: true}, features) + + routeRegister := routing.NewRouteRegister() + importDashboardAPI.RegisterAPIEndpoints(routeRegister) + s := webtest.NewServer(t, routeRegister) + + cmd := &dashboardimport.ImportDashboardRequest{ + Dashboard: simplejson.New(), + } + jsonBytes, err := json.Marshal(cmd) + require.NoError(t, err) + req := s.NewPostRequest("/api/dashboards/interpolate", bytes.NewReader(jsonBytes)) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{ + UserID: 1, + Permissions: map[int64]map[string][]string{ + 1: {dashboards.ActionDashboardsCreate: {}}, + }, + }) + resp, err := s.SendJSON(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + require.Equal(t, http.StatusNotFound, resp.StatusCode) + }) + + t.Run("Feature flag enabled - interpolate endpoint should work", func(t *testing.T) { + interpolateDashboardServiceCalled := false + service := &serviceMock{ + interpolateDashboardFunc: func(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*simplejson.Json, error) { + interpolateDashboardServiceCalled = true + return simplejson.New(), nil + }, + } + // Create features with dashboardLibrary enabled + features := featuremgmt.WithFeatures(featuremgmt.FlagDashboardLibrary) + importDashboardAPI := New(service, quotaServiceFunc(quotaNotReached), nil, actest.FakeAccessControl{ExpectedEvaluate: true}, features) + + routeRegister := routing.NewRouteRegister() + importDashboardAPI.RegisterAPIEndpoints(routeRegister) + s := webtest.NewServer(t, routeRegister) + + cmd := &dashboardimport.ImportDashboardRequest{ + PluginId: "test-plugin", + } + jsonBytes, err := json.Marshal(cmd) + require.NoError(t, err) + req := s.NewPostRequest("/api/dashboards/interpolate", bytes.NewReader(jsonBytes)) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{ + UserID: 1, + Permissions: map[int64]map[string][]string{ + 1: {dashboards.ActionDashboardsCreate: {}}, + }, + }) + resp, err := s.SendJSON(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.True(t, interpolateDashboardServiceCalled) + }) +} + type serviceMock struct { - importDashboardFunc func(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) + importDashboardFunc func(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) + interpolateDashboardFunc func(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*simplejson.Json, error) } func (s *serviceMock) ImportDashboard(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) { @@ -171,6 +238,14 @@ func (s *serviceMock) ImportDashboard(ctx context.Context, req *dashboardimport. return nil, nil } +func (s *serviceMock) InterpolateDashboard(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*simplejson.Json, error) { + if s.interpolateDashboardFunc != nil { + return s.interpolateDashboardFunc(ctx, req) + } + + return simplejson.New(), nil +} + func quotaReached(c *contextmodel.ReqContext, target quota.TargetSrv) (bool, error) { return true, nil } diff --git a/pkg/services/dashboardimport/dashboardimport.go b/pkg/services/dashboardimport/dashboardimport.go index d16e0230bf5..d21d449cc0b 100644 --- a/pkg/services/dashboardimport/dashboardimport.go +++ b/pkg/services/dashboardimport/dashboardimport.go @@ -52,4 +52,5 @@ type ImportDashboardResponse struct { // Service service interface for importing dashboards. type Service interface { ImportDashboard(ctx context.Context, req *ImportDashboardRequest) (*ImportDashboardResponse, error) + InterpolateDashboard(ctx context.Context, req *ImportDashboardRequest) (*simplejson.Json, error) } diff --git a/pkg/services/dashboardimport/service/service.go b/pkg/services/dashboardimport/service/service.go index fac501f9361..8e78bdcac3d 100644 --- a/pkg/services/dashboardimport/service/service.go +++ b/pkg/services/dashboardimport/service/service.go @@ -34,7 +34,7 @@ func ProvideService(routeRegister routing.RouteRegister, features: features, } - dashboardImportAPI := api.New(s, quotaService, pluginStore, ac) + dashboardImportAPI := api.New(s, quotaService, pluginStore, ac, features) dashboardImportAPI.RegisterAPIEndpoints(routeRegister) return s @@ -48,7 +48,7 @@ type ImportDashboardService struct { features featuremgmt.FeatureToggles } -func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) { +func (s *ImportDashboardService) InterpolateDashboard(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*simplejson.Json, error) { var draftDashboard *dashboards.Dashboard if req.PluginId != "" { loadReq := &plugindashboards.LoadPluginDashboardRequest{ @@ -70,6 +70,15 @@ func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashb return nil, err } + return generatedDash, nil +} + +func (s *ImportDashboardService) ImportDashboard(ctx context.Context, req *dashboardimport.ImportDashboardRequest) (*dashboardimport.ImportDashboardResponse, error) { + generatedDash, err := s.InterpolateDashboard(ctx, req) + if err != nil { + return nil, err + } + // Maintain backwards compatibility by transforming array of library elements to map libraryElements := generatedDash.Get("__elements") libElementsArr, err := libraryElements.Array() diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 06581c7e601..1ed039f3af0 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -966,7 +966,7 @@ var ( Description: "Enable suggested dashboards when creating new dashboards", Stage: FeatureStageExperimental, Owner: grafanaSharingSquad, - FrontendOnly: true, + FrontendOnly: false, }, { Name: "logsExploreTableDefaultVisualization", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index b54bf6df753..e3dd5c8263a 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -125,7 +125,7 @@ cloudWatchNewLabelParsing,GA,@grafana/aws-datasources,false,false,false disableNumericMetricsSortingInExpressions,experimental,@grafana/oss-big-tent,false,true,false grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,false queryLibrary,preview,@grafana/sharing-squad,false,false,false -dashboardLibrary,experimental,@grafana/sharing-squad,false,false,true +dashboardLibrary,experimental,@grafana/sharing-squad,false,false,false logsExploreTableDefaultVisualization,experimental,@grafana/observability-logs,false,false,true alertingListViewV2,privatePreview,@grafana/alerting-squad,false,false,true alertingDisableSendAlertsExternal,experimental,@grafana/alerting-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index e6dda337118..ef4b6aac2f8 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1082,14 +1082,16 @@ { "metadata": { "name": "dashboardLibrary", - "resourceVersion": "1758902532246", - "creationTimestamp": "2025-09-26T16:02:12Z" + "resourceVersion": "1760051989635", + "creationTimestamp": "2025-09-26T16:02:12Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-10-09 23:19:49.635811 +0000 UTC" + } }, "spec": { "description": "Enable suggested dashboards when creating new dashboards", "stage": "experimental", - "codeowner": "@grafana/sharing-squad", - "frontend": true + "codeowner": "@grafana/sharing-squad" } }, { diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index 0f25b53a11a..46c47a02e82 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -10068,6 +10068,10 @@ "$ref": "#/definitions/publicError" } }, + "interpolateDashboardResponse": { + "description": "", + "schema": {} + }, "jwksResponse": { "description": "", "schema": { diff --git a/public/api-merged.json b/public/api-merged.json index ef550058eec..83211911e0d 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -3671,6 +3671,32 @@ } } }, + "/dashboards/interpolate": { + "post": { + "tags": [ + "dashboards" + ], + "summary": "Interpolate dashboard. This is an experimental endpoint under dashboardLibrary FF and is subject to change.", + "operationId": "interpolateDashboard", + "responses": { + "200": { + "$ref": "#/responses/interpolateDashboardResponse" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "422": { + "$ref": "#/responses/unprocessableEntityError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, "/dashboards/public-dashboards": { "get": { "description": "Get list of public dashboards", @@ -25139,6 +25165,10 @@ "$ref": "#/definitions/publicError" } }, + "interpolateDashboardResponse": { + "description": "(empty)", + "schema": {} + }, "jwksResponse": { "description": "(empty)", "schema": { diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 00c02b5d727..54dd386c619 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -1,6 +1,6 @@ import { locationUtil, UrlQueryMap } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { config, getBackendSrv, isFetchError, locationService } from '@grafana/runtime'; +import { config, getBackendSrv, getDataSourceSrv, isFetchError, locationService } from '@grafana/runtime'; import { sceneGraph } from '@grafana/scenes'; import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { GetRepositoryFilesWithPathApiResponse, provisioningAPIv0alpha1 } from 'app/api/clients/provisioning/v0alpha1'; @@ -415,6 +415,13 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag if (rsp?.dashboard) { const scene = transformSaveModelToScene(rsp); + // Special handling for Template route - set up edit mode and dirty state + if (config.featureToggles.dashboardLibrary && options.route === DashboardRoutes.Template) { + scene.setInitialSaveModel(rsp.dashboard, rsp.meta); + scene.onEnterEditMode(); + scene.setState({ isDirty: true }); + } + // Cache scene only if not coming from Explore, we don't want to cache temporary dashboard if (options.uid) { this.setSceneCache(options.uid, scene); @@ -441,6 +448,57 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag throw new Error('Snapshot not found'); } + private async loadTemplateDashboard(): Promise<DashboardDTO> { + // Extract template parameters from URL + const searchParams = new URLSearchParams(window.location.search); + const datasource = searchParams.get('datasource'); + const pluginId = searchParams.get('pluginId'); + const path = searchParams.get('path'); + + if (!datasource || !pluginId || !path) { + throw new Error('Missing required parameters for template dashboard'); + } + + const ds = getDataSourceSrv().getInstanceSettings(datasource); + if (!ds) { + throw new Error(`Datasource "${datasource}" not found. Please check your datasource configuration.`); + } + + const data = { + pluginId, + path, + overwrite: true, + inputs: [ + { + name: '*', + type: 'datasource', + pluginId, + value: datasource, + }, + ], + }; + + const interpolatedDashboard = await getBackendSrv().post('/api/dashboards/interpolate', data); + + return { + dashboard: { + ...interpolatedDashboard, + uid: '', + version: 0, + id: null, + }, + meta: { + canSave: true, + canEdit: true, + canStar: false, + canShare: false, + canDelete: false, + isNew: true, + folderUid: '', + }, + }; + } + public async fetchDashboard({ type, slug, @@ -474,6 +532,9 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag case DashboardRoutes.New: rsp = await buildNewDashboardSaveModel(urlFolderUid); break; + case DashboardRoutes.Template: + rsp = await this.loadTemplateDashboard(); + break; case DashboardRoutes.Provisioning: return this.loadProvisioningDashboard(slug || '', uid); case DashboardRoutes.Public: { diff --git a/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx b/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx index 2b7624b779a..d86f8309b31 100644 --- a/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx +++ b/public/app/features/dashboard-scene/saving/DashboardPrompt.test.tsx @@ -1,12 +1,47 @@ import { SceneQueryRunner, SceneTimeRange, VizPanel, behaviors } from '@grafana/scenes'; +import { Dashboard } from '@grafana/schema'; +import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { ContextSrv, setContextSrv } from 'app/core/services/context_srv'; +import { ObjectMeta } from 'app/features/apiserver/types'; import { DashboardControls } from '../scene/DashboardControls'; import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager'; import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; -import { ignoreChanges } from './DashboardPrompt'; +import { ignoreChanges, isEmptyDashboard } from './DashboardPrompt'; + +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + config: { + ...jest.requireActual('@grafana/runtime').config, + defaultDatasource: 'gdev-testdata', + datasources: { + 'gdev-testdata': { + id: 1, + uid: 'gdev-testdata', + type: 'grafana-testdata-datasource', + name: 'gdev-testdata', + meta: { + id: 'grafana-testdata-datasource', + type: 'datasource', + name: 'TestData', + }, + }, + '-- Grafana --': { + id: -1, + uid: 'grafana', + type: 'datasource', + name: '-- Grafana --', + meta: { + id: 'grafana', + type: 'datasource', + name: '-- Grafana --', + }, + }, + }, + }, +})); function getTestContext() { const contextSrv = { isSignedIn: true, isEditor: true } as ContextSrv; @@ -130,30 +165,288 @@ describe('DashboardPrompt', () => { }); }); }); + + describe('isEmptyDashboard', () => { + describe('Dashboard V1 tests', () => { + describe('empty dashboard cases', () => { + it('should return true for completely empty dashboard', () => { + const emptyDashboard: Dashboard = { + id: null, + uid: '', + title: '', + tags: [], + panels: [], + schemaVersion: 16, + version: 0, + links: [], + time: { from: 'now-6h', to: 'now' }, + timepicker: {}, + templating: { list: [] }, + annotations: { list: [] }, + }; + + expect(isEmptyDashboard(emptyDashboard)).toBe(true); + }); + + it('should return true for dashboard with no panels, links, templates, or uid', () => { + const scene = buildTestScene( + { + uid: '', + body: DefaultGridLayoutManager.fromVizPanels([]), + }, + 'v1' + ); + const dashboard = scene.getSaveModel() as Dashboard; + dashboard.links = []; + dashboard.templating = { list: [] }; + dashboard.uid = ''; + + expect(isEmptyDashboard(dashboard)).toBe(true); + }); + }); + + describe('non-empty dashboard cases', () => { + it('should return false for dashboard with panels', () => { + const scene = buildTestScene(); + const dashboard = scene.getSaveModel(); + + expect(isEmptyDashboard(dashboard)).toBe(false); + }); + + it('should return false for dashboard with links', () => { + const scene = buildTestScene( + { + uid: '', + body: DefaultGridLayoutManager.fromVizPanels([]), + links: [ + { + title: 'Test Link', + url: 'https://example.com', + type: 'link', + icon: 'external link', + tooltip: '', + asDropdown: false, + tags: [], + includeVars: false, + keepTime: false, + targetBlank: false, + }, + ], + }, + 'v1' + ); + const dashboard = scene.getSaveModel() as Dashboard; + dashboard.templating = { list: [] }; + + expect(isEmptyDashboard(dashboard)).toBe(false); + }); + + it('should return false for dashboard with template variables', () => { + const scene = buildTestScene( + { + uid: '', + body: DefaultGridLayoutManager.fromVizPanels([]), + }, + 'v1' + ); + const dashboard = scene.getSaveModel() as Dashboard; + dashboard.links = []; + dashboard.templating = { + list: [ + { + name: 'testVar', + type: 'query', + query: 'test query', + current: { value: 'test', text: 'test' }, + options: [], + }, + ], + }; + + expect(isEmptyDashboard(dashboard)).toBe(false); + }); + + it('should return false for dashboard with uid', () => { + const scene = buildTestScene( + { + uid: 'test-uid-123', + body: DefaultGridLayoutManager.fromVizPanels([]), + }, + 'v1' + ); + const dashboard = scene.getSaveModel() as Dashboard; + dashboard.links = []; + dashboard.templating = { list: [] }; + + expect(isEmptyDashboard(dashboard)).toBe(false); + }); + }); + }); + + describe('Dashboard V2 tests', () => { + describe('empty dashboard cases', () => { + it('should return true for completely empty dashboard v2', () => { + const emptyDashboardV2: DashboardV2Spec = { + title: '', + tags: [], + elements: {}, + layout: { + kind: 'GridLayout', + spec: { + items: [], + }, + }, + links: [], + variables: [], + annotations: [], + timeSettings: { + from: 'now-6h', + to: 'now', + timezone: 'browser', + weekStart: 'monday', + fiscalYearStartMonth: 0, + autoRefreshIntervals: ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'], + autoRefresh: '5s', + hideTimepicker: false, + }, + cursorSync: 'Off', + liveNow: false, + preload: false, + }; + const emptyMetadata: ObjectMeta = { + name: '', + resourceVersion: '1', + creationTimestamp: '2023-01-01T00:00:00Z', + }; + + expect(isEmptyDashboard(emptyDashboardV2, emptyMetadata)).toBe(true); + }); + + it('should return true for dashboard v2 with no elements, links, variables, or name', () => { + const scene = buildTestScene( + { + body: DefaultGridLayoutManager.fromVizPanels([]), + }, + 'v2' + ); + const dashboard = scene.getSaveModel(); + const metadata: ObjectMeta = { + name: '', + resourceVersion: '1', + creationTimestamp: '2023-01-01T00:00:00Z', + }; + + expect(isEmptyDashboard(dashboard, metadata)).toBe(true); + }); + }); + + describe('non-empty dashboard cases', () => { + it('should return false for dashboard v2 with elements', () => { + const scene = buildTestScene({}, 'v2'); + const dashboard = scene.getSaveModel(); + + expect(isEmptyDashboard(dashboard)).toBe(false); + }); + + it('should return false for dashboard v2 with links', () => { + const scene = buildTestScene( + { + body: DefaultGridLayoutManager.fromVizPanels([]), + links: [ + { + title: 'Test Link V2', + url: 'https://example.com', + type: 'link', + icon: 'external link', + tooltip: '', + asDropdown: false, + tags: [], + includeVars: false, + keepTime: false, + targetBlank: false, + }, + ], + }, + 'v2' + ); + const dashboard = scene.getSaveModel(); + const metadata: ObjectMeta = { + name: '', + resourceVersion: '1', + creationTimestamp: '2023-01-01T00:00:00Z', + }; + + expect(isEmptyDashboard(dashboard, metadata)).toBe(false); + }); + + it('should return false for dashboard v2 with variables', () => { + const scene = buildTestScene( + { + body: DefaultGridLayoutManager.fromVizPanels([]), + }, + 'v2' + ); + const dashboard = scene.getSaveModel() as DashboardV2Spec; + dashboard.variables = [ + { kind: 'ConstantVariable', spec: { name: 'testVar' } } as DashboardV2Spec['variables'][number], + ]; + const metadata: ObjectMeta = { + name: '', + resourceVersion: '1', + creationTimestamp: '2023-01-01T00:00:00Z', + }; + + expect(isEmptyDashboard(dashboard, metadata)).toBe(false); + }); + + it('should return false for dashboard v2 with name in metadata', () => { + const scene = buildTestScene( + { + body: DefaultGridLayoutManager.fromVizPanels([]), + }, + 'v2' + ); + const dashboard = scene.getSaveModel(); + const metadata: ObjectMeta = { + name: 'test-dashboard-with-name', + resourceVersion: '1', + creationTimestamp: '2023-01-01T00:00:00Z', + }; + + expect(isEmptyDashboard(dashboard, metadata)).toBe(false); + }); + }); + }); + }); }); -function buildTestScene(overrides?: Partial<DashboardSceneState>) { - const scene = new DashboardScene({ - title: 'hello', - uid: 'dash-1', - description: 'hello description', - tags: ['tag1', 'tag2'], - editable: true, - $timeRange: new SceneTimeRange({ - timeZone: 'browser', +function buildTestScene(overrides?: Partial<DashboardSceneState>, serializerVersion: 'v1' | 'v2' = 'v1') { + const defaultPanels = [ + new VizPanel({ + title: 'Panel A', + key: 'panel-1', + pluginId: 'table', + $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }), }), - controls: new DashboardControls({}), - $behaviors: [new behaviors.CursorSync({})], - body: DefaultGridLayoutManager.fromVizPanels([ - new VizPanel({ - title: 'Panel A', - key: 'panel-1', - pluginId: 'table', - $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }), + ]; + + const scene = new DashboardScene( + { + title: 'hello', + uid: 'dash-1', + description: 'hello description', + tags: ['tag1', 'tag2'], + editable: true, + $timeRange: new SceneTimeRange({ + timeZone: 'browser', }), - ]), - ...overrides, - }); + controls: new DashboardControls({}), + $behaviors: [new behaviors.CursorSync({})], + body: DefaultGridLayoutManager.fromVizPanels(defaultPanels), + ...overrides, + }, + serializerVersion + ); return scene; } diff --git a/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx b/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx index 203ddc40783..134e2cedbab 100644 --- a/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx +++ b/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx @@ -4,9 +4,15 @@ import { memo, useContext, useEffect, useMemo } from 'react'; import { Trans, t } from '@grafana/i18n'; import { locationService } from '@grafana/runtime'; +import { Dashboard } from '@grafana/schema'; +import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { ModalsContext, Modal, Button, useStyles2 } from '@grafana/ui'; import { Prompt } from 'app/core/components/FormPrompt/Prompt'; import { contextSrv } from 'app/core/services/context_srv'; +import { ObjectMeta } from 'app/features/apiserver/types'; +import { isDashboardV2Spec } from 'app/features/dashboard/api/utils'; +import { DASHBOARD_LIBRARY_ROUTES } from 'app/features/dashboard/dashgrid/types'; +import { DashboardMeta } from 'app/types/dashboard'; import { SaveLibraryVizPanelModal } from '../panel-edit/SaveLibraryVizPanelModal'; import { DashboardScene } from '../scene/DashboardScene'; @@ -18,7 +24,8 @@ interface DashboardPromptProps { export const DashboardPrompt = memo(({ dashboard }: DashboardPromptProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps - const originalPath = useMemo(() => locationService.getLocation().pathname, [dashboard]); + const originalLocation = useMemo(() => locationService.getLocation(), [dashboard]); + const originalPath = useMemo(() => originalLocation.pathname, [originalLocation]); const { showModal, hideModal } = useContext(ModalsContext); useEffect(() => { @@ -94,7 +101,11 @@ export const DashboardPrompt = memo(({ dashboard }: DashboardPromptProps) => { onDiscard: () => { dashboard.exitEditMode({ skipConfirm: true }); hideModal(); - moveToBlockedLocationAfterReactStateUpdate(location); + if (originalPath === DASHBOARD_LIBRARY_ROUTES.Template) { + moveToBlockedLocationAfterReactStateUpdate(location, true); + } else { + moveToBlockedLocationAfterReactStateUpdate(location); + } }, onDismiss: hideModal, }); @@ -107,9 +118,9 @@ export const DashboardPrompt = memo(({ dashboard }: DashboardPromptProps) => { DashboardPrompt.displayName = 'DashboardPrompt'; -function moveToBlockedLocationAfterReactStateUpdate(location?: H.Location | null) { +function moveToBlockedLocationAfterReactStateUpdate(location?: H.Location | null, replace = false) { if (location) { - setTimeout(() => locationService.push(location), 10); + setTimeout(() => (replace ? locationService.replace(location) : locationService.push(location)), 10); } } @@ -178,6 +189,12 @@ export function ignoreChanges(scene: DashboardScene | null) { return true; } + const dashboard = scene.getSaveModel(); + // Ignore changes if the dashboard is empty (new dashboard) + if (isEmptyDashboard(dashboard, scene?.serializer.metadata)) { + return true; + } + const { canSave, fromScript, fromFile } = scene.state.meta; if (!contextSrv.isEditor && !canSave) { return true; @@ -185,3 +202,24 @@ export function ignoreChanges(scene: DashboardScene | null) { return !canSave || fromScript || fromFile; } + +export function isEmptyDashboard( + dashboard: Dashboard | DashboardV2Spec, + metadata?: DashboardMeta | ObjectMeta +): boolean { + if (isDashboardV2Spec(dashboard)) { + const hasNoPanels = Object.keys(dashboard.elements).length === 0; + const hasNoLinks = !dashboard.links.length; + const hasNoTemplates = !dashboard.variables.length; + const hasNoUid = !metadata || !('name' in metadata) || !metadata.name; + + return hasNoPanels && hasNoLinks && hasNoTemplates && hasNoUid; + } + + const hasNoPanels = !dashboard.panels?.length; + const hasNoLinks = !dashboard.links?.length; + const hasNoTemplates = !dashboard.templating?.list?.length; + const hasNoUid = !dashboard.uid; + + return hasNoPanels && hasNoLinks && hasNoTemplates && hasNoUid; +} diff --git a/public/app/features/dashboard-scene/utils/interactions.ts b/public/app/features/dashboard-scene/utils/interactions.ts index 1d02c729676..e95040ae2c8 100644 --- a/public/app/features/dashboard-scene/utils/interactions.ts +++ b/public/app/features/dashboard-scene/utils/interactions.ts @@ -4,6 +4,13 @@ import { DashboardTrackingInfo, DynamicDashboardsTrackingInformation } from '../ let isScenesContextSet = false; +type DashboardLibraryTrackingInfo = { + pluginId?: string; + sourceEntryPoint?: string; + libraryItemId?: string; + creationOrigin?: string; +}; + export const DashboardInteractions = { // Dashboard interactions: dashboardInitialized: ( @@ -21,8 +28,8 @@ export const DashboardInteractions = { dashboardCreatedOrSaved: ( isNew: boolean | undefined, properties: - | { name: string; url: string } - | { + | ({ name: string; url: string } & DashboardLibraryTrackingInfo) + | ({ name: string; url: string; numPanels: number; @@ -31,7 +38,7 @@ export const DashboardInteractions = { autoLayoutCount: number; customGridLayoutCount: number; panelsByDatasourceType: Record<string, number>; - } + } & DashboardLibraryTrackingInfo) ) => { reportDashboardInteraction(isNew ? 'created' : 'saved', properties, 'grafana_dashboard'); }, diff --git a/public/app/features/dashboard-scene/utils/tracking.ts b/public/app/features/dashboard-scene/utils/tracking.ts index 30b38596346..21f770daca0 100644 --- a/public/app/features/dashboard-scene/utils/tracking.ts +++ b/public/app/features/dashboard-scene/utils/tracking.ts @@ -1,4 +1,5 @@ import { store } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { DashboardScene } from '../scene/DashboardScene'; import { EditableDashboardElementInfo } from '../scene/types/EditableDashboardElement'; @@ -52,8 +53,24 @@ export function trackDashboardSceneCreatedOrSaved( dashboard: DashboardScene, initialProperties: { name: string; url: string } ) { + // url values for dashboard library experiment + const urlParams = new URLSearchParams(window.location.search); + const pluginId = urlParams.get('pluginId') || undefined; + const sourceEntryPoint = urlParams.get('sourceEntryPoint') || undefined; + const libraryItemId = urlParams.get('libraryItemId') || undefined; + const creationOrigin = urlParams.get('creationOrigin') || undefined; + const dynamicDashboardsTrackingInformation = dashboard.getDynamicDashboardsTrackingInformation(); + const dashboardLibraryProperties = config.featureToggles.dashboardLibrary + ? { + datasourceTypes: [pluginId], + sourceEntryPoint, + libraryItemId, + creationOrigin, + } + : {}; + DashboardInteractions.dashboardCreatedOrSaved(isNew, { ...initialProperties, ...(dynamicDashboardsTrackingInformation @@ -64,7 +81,10 @@ export function trackDashboardSceneCreatedOrSaved( autoLayoutCount: dynamicDashboardsTrackingInformation.autoLayoutCount, customGridLayoutCount: dynamicDashboardsTrackingInformation.customGridLayoutCount, panelsByDatasourceType: dynamicDashboardsTrackingInformation.panelsByDatasourceType, + ...dashboardLibraryProperties, } - : {}), + : { + ...dashboardLibraryProperties, + }), }); } diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.tsx index 2e37c4b3749..5409ff7177b 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.tsx @@ -32,7 +32,9 @@ function DashboardPageProxy(props: DashboardPageProxyProps) { } const isScenesSupportedRoute = Boolean( - props.route.routeName === DashboardRoutes.Home || (props.route.routeName === DashboardRoutes.Normal && params.uid) + props.route.routeName === DashboardRoutes.Home || + props.route.routeName === DashboardRoutes.Template || + (props.route.routeName === DashboardRoutes.Normal && params.uid) ); // We pre-fetch dashboard to render dashboard page component depending on dashboard permissions. diff --git a/public/app/features/dashboard/containers/NewDashboardWithDS.tsx b/public/app/features/dashboard/containers/NewDashboardWithDS.tsx index 46064030ca2..04b2bbcaa41 100644 --- a/public/app/features/dashboard/containers/NewDashboardWithDS.tsx +++ b/public/app/features/dashboard/containers/NewDashboardWithDS.tsx @@ -22,7 +22,7 @@ export default function NewDashboardWithDS() { dispatch(setInitialDatasource(datasourceUid)); - locationService.replace('/dashboard/new'); + locationService.replace(`/dashboard/new?dashboardLibraryDatasourceUid=${datasourceUid}`); }, [datasourceUid, dispatch]); if (error) { diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.test.tsx b/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.test.tsx index 4c6ed9d33e3..61cc488109c 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.test.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.test.tsx @@ -1,4 +1,5 @@ -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { act, fireEvent, screen } from '@testing-library/react'; +import { render } from 'test/test-utils'; import { config, locationService, reportInteraction } from '@grafana/runtime'; import { defaultDashboard } from '@grafana/schema'; @@ -18,6 +19,9 @@ jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), locationService: { partial: jest.fn(), + getHistory: jest.fn(() => ({ + listen: jest.fn(), + })), }, reportInteraction: jest.fn(), })); @@ -36,6 +40,10 @@ jest.mock('app/features/provisioning/hooks/useGetResourceRepositoryView', () => })), })); +jest.mock('../DashboardLibrary/DashboardLibrarySection', () => ({ + DashboardLibrarySection: () => <div data-testid="dashboard-library-section">Dashboard Library Section</div>, +})); + const mockUseGetResourceRepositoryView = jest.mocked( require('app/features/provisioning/hooks/useGetResourceRepositoryView').useGetResourceRepositoryView ); @@ -154,6 +162,44 @@ it('renders with buttons disabled when repository is read-only', () => { expect(screen.getByRole('button', { name: 'Add library panel' })).toBeDisabled(); }); +describe('DashboardLibrarySection feature toggle', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockUseGetResourceRepositoryView.mockReturnValue({ + isReadOnlyRepo: false, + isInstanceManaged: false, + isLoading: false, + }); + }); + + it('renders DashboardLibrarySection when feature toggle is enabled and dashboardLibraryDatasourceUid param exists', () => { + config.featureToggles.dashboardLibrary = true; + mockSearchParams.set('dashboardLibraryDatasourceUid', 'test-uid'); + + setup(); + + expect(screen.getByTestId('dashboard-library-section')).toBeInTheDocument(); + }); + + it('does not render DashboardLibrarySection when feature toggle is disabled', () => { + config.featureToggles.dashboardLibrary = false; + mockSearchParams.delete('dashboardLibraryDatasourceUid'); + + setup(); + + expect(screen.queryByTestId('dashboard-library-section')).not.toBeInTheDocument(); + }); + + it('does not render DashboardLibrarySection when feature toggle is enabled but no dashboardLibraryDatasourceUid param', () => { + config.featureToggles.dashboardLibrary = true; + mockSearchParams.delete('dashboardLibraryDatasourceUid'); + + setup(); + + expect(screen.queryByTestId('dashboard-library-section')).not.toBeInTheDocument(); + }); +}); + describe('wrapperMaxWidth CSS class', () => { beforeEach(() => { jest.clearAllMocks(); diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.tsx b/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.tsx index 6369831a12c..8e473124434 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmpty.tsx @@ -10,6 +10,8 @@ import { Button, useStyles2, Text, Box, Stack, TextLink } from '@grafana/ui'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; +import { DashboardLibrarySection } from '../DashboardLibrary/DashboardLibrarySection'; + import { DashboardEmptyExtensionPoint } from './DashboardEmptyExtensionPoint'; import { useIsReadOnlyRepo, @@ -64,6 +66,7 @@ const InternalDashboardEmpty = ({ onAddVisualization, onAddLibraryPanel, onImpor </Button> </Stack> </Box> + {config.featureToggles.dashboardLibrary && dashboardLibraryDatasourceUid && <DashboardLibrarySection />} <Stack direction={{ xs: 'column', md: 'row' }} wrap="wrap" gap={4}> <Box borderRadius="lg" borderColor="strong" borderStyle="dashed" padding={3} flex={1}> <Stack direction="column" alignItems="center" gap={1}> diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/DashboardLibrarySection.tsx b/public/app/features/dashboard/dashgrid/DashboardLibrary/DashboardLibrarySection.tsx new file mode 100644 index 00000000000..f0df493ff1e --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/DashboardLibrarySection.tsx @@ -0,0 +1,199 @@ +import { css } from '@emotion/css'; +import { useState } from 'react'; +import { useSearchParams } from 'react-router-dom-v5-compat'; +import { useAsync } from 'react-use'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Trans } from '@grafana/i18n'; +import { getBackendSrv, getDataSourceSrv, locationService } from '@grafana/runtime'; +import { Button, useStyles2, Text, Box, Stack, Grid } from '@grafana/ui'; +import { PluginDashboard } from 'app/types/plugins'; +import dashboardLibrary1 from 'img/dashboard-library/dashboard_library_1.jpg'; +import dashboardLibrary2 from 'img/dashboard-library/dashboard_library_2.jpg'; +import dashboardLibrary3 from 'img/dashboard-library/dashboard_library_3.jpg'; +import dashboardLibrary4 from 'img/dashboard-library/dashboard_library_4.jpg'; +import dashboardLibrary5 from 'img/dashboard-library/dashboard_library_5.jpg'; +import dashboardLibrary6 from 'img/dashboard-library/dashboard_library_6.jpg'; + +import { DASHBOARD_LIBRARY_ROUTES } from '../types'; + +import { DashboardLibraryInteractions } from './interactions'; + +export const DashboardLibrarySection = () => { + const [searchParams] = useSearchParams(); + const datasourceUid = searchParams.get('dashboardLibraryDatasourceUid'); + + const [showAll, setShowAll] = useState(false); + + const { value: templateDashboards } = useAsync(async (): Promise<PluginDashboard[]> => { + if (!datasourceUid) { + return []; + } + + const ds = getDataSourceSrv().getInstanceSettings(datasourceUid); + if (!ds) { + return []; + } + + try { + const dashboards = await getBackendSrv().get(`api/plugins/${ds.type}/dashboards`, undefined, undefined, { + showErrorAlert: false, + }); + + if (dashboards.length > 0) { + DashboardLibraryInteractions.loaded({ + numberOfItems: dashboards.length, + contentKinds: ['datasource_dashboard'], + datasourceTypes: [ds.type], + sourceEntryPoint: 'datasource_page', + }); + } + return dashboards; + } catch (error) { + console.error('Error loading template dashboards', error); + return []; + } + }, [datasourceUid]); + + const hasMoreThanThree = templateDashboards && templateDashboards.length > 3; + const dashboardsToShow = showAll ? templateDashboards : templateDashboards?.slice(0, 3); + + const styles = useStyles2(getStyles, dashboardsToShow?.length); + + const onImportDashboardClick = async (dashboard: PluginDashboard) => { + DashboardLibraryInteractions.itemClicked({ + contentKind: 'datasource_dashboard', + datasourceTypes: [dashboard.pluginId], + libraryItemId: dashboard.uid, + libraryItemTitle: dashboard.title, + sourceEntryPoint: 'datasource_page', + }); + + const params = new URLSearchParams({ + datasource: datasourceUid || '', + title: dashboard.title || 'Template', + pluginId: dashboard.pluginId, + path: dashboard.path, + // tracking event purpose values + sourceEntryPoint: 'datasource_page', + libraryItemId: dashboard.uid, + creationOrigin: 'dashboard_library_datasource_dashboard', + }); + + const templateUrl = `${DASHBOARD_LIBRARY_ROUTES.Template}?${params.toString()}`; + locationService.push(templateUrl); + }; + + if (!templateDashboards?.length) { + return null; + } + + return ( + <Box borderColor="strong" borderStyle="dashed" padding={4} flex={1}> + <Stack direction="column" alignItems="center" gap={2}> + <Text element="h3" textAlignment="center" weight="medium"> + <Trans i18nKey="dashboard.empty.start-with-suggested-dashboards"> + Start with a pre-made dashboard from your data source + </Trans> + </Text> + <Box marginTop={2}> + <Grid + gap={4} + columns={{ + xs: 1, + sm: (dashboardsToShow?.length || 1) >= 2 ? 2 : 1, + lg: (dashboardsToShow?.length || 1) >= 3 ? 3 : (dashboardsToShow?.length || 1) >= 2 ? 2 : 1, + }} + > + {dashboardsToShow?.map((dashboard, index) => ( + <TemplateDashboardBox + key={dashboard.uid} + index={index} + dashboard={dashboard} + onImportClick={onImportDashboardClick} + /> + )) || []} + </Grid> + </Box> + {hasMoreThanThree && ( + <Button + variant="secondary" + fill="outline" + size="sm" + onClick={() => setShowAll((prev) => !prev)} + className={styles.showMoreButton} + > + {showAll ? ( + <Trans i18nKey="dashboard.empty.show-less-dashboards">Show less</Trans> + ) : ( + <Trans i18nKey="dashboard.empty.show-more-dashboards">Show more</Trans> + )} + </Button> + )} + </Stack> + </Box> + ); +}; + +const TemplateDashboardBox = ({ + dashboard, + onImportClick, + index, +}: { + dashboard: PluginDashboard; + onImportClick: (d: PluginDashboard) => void; + index: number; +}) => { + const dashboardLibraryImages = [ + dashboardLibrary1, + dashboardLibrary2, + dashboardLibrary3, + dashboardLibrary4, + dashboardLibrary5, + dashboardLibrary6, + ]; + + const styles = useStyles2(getStyles); + return ( + <div className={styles.templateDashboardBox}> + <img + src={index <= 5 ? dashboardLibraryImages[index] : dashboardLibraryImages[index % dashboardLibraryImages.length]} + width={285} + height={160} + alt={dashboard.title} + className={styles.templateDashboardImage} + /> + <div className={styles.templateDashboardTitle}> + <Text element="p" textAlignment="center"> + {dashboard.title} + </Text> + </div> + <Button fill="outline" onClick={() => onImportClick(dashboard)} size="sm"> + <Trans i18nKey="dashboard.empty.use-template-button">Use this dashboard</Trans> + </Button> + </div> + ); +}; + +function getStyles(theme: GrafanaTheme2, dashboardsLength?: number) { + return { + templateDashboardBox: css({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), + alignItems: 'center', + }), + templateDashboardTitle: css({ + flex: 1, + }), + templateDashboardImage: css({ + borderRadius: theme.shape.radius.default, + borderColor: theme.colors.text.primary, + borderWidth: 1, + borderStyle: 'solid', + }), + showMoreButton: css({ + marginTop: theme.spacing(2), + }), + }; +} diff --git a/public/app/features/dashboard/dashgrid/DashboardLibrary/interactions.ts b/public/app/features/dashboard/dashgrid/DashboardLibrary/interactions.ts new file mode 100644 index 00000000000..1222007dd84 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/DashboardLibrary/interactions.ts @@ -0,0 +1,35 @@ +import { reportInteraction } from '@grafana/runtime'; + +const SCHEMA_VERSION = 1; + +type ContentKind = 'datasource_dashboard'; +// in future this could be "template_dashboard" if/when items become templates or "community_dashboard" +// | 'template_dashboard' | 'community_dashboard'; + +type SourceEntryPoint = 'datasource_page'; +// possible future flows onboarding, create-dashboard, empty states +// | 'create_dashboard' | 'empty_state'; + +export const DashboardLibraryInteractions = { + loaded: (properties: { + numberOfItems: number; + contentKinds: ContentKind[]; + datasourceTypes: string[]; + sourceEntryPoint: SourceEntryPoint; + }) => { + reportDashboardLibraryInteraction('loaded', properties); + }, + itemClicked: (properties: { + contentKind: ContentKind; + datasourceTypes: string[]; + libraryItemId: string; + libraryItemTitle: string; + sourceEntryPoint: SourceEntryPoint; + }) => { + reportDashboardLibraryInteraction('item_clicked', properties); + }, +}; + +const reportDashboardLibraryInteraction = (name: string, properties?: Record<string, unknown>) => { + reportInteraction(`grafana_dashboard_library_${name}`, { ...properties, schema_version: SCHEMA_VERSION }); +}; diff --git a/public/app/features/dashboard/dashgrid/types.ts b/public/app/features/dashboard/dashgrid/types.ts new file mode 100644 index 00000000000..2549ddbe40d --- /dev/null +++ b/public/app/features/dashboard/dashgrid/types.ts @@ -0,0 +1,3 @@ +export const DASHBOARD_LIBRARY_ROUTES = { + Template: '/dashboard/template', +}; diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index 47e3fcfa90b..2448eda99e1 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -12,6 +12,7 @@ import { isAdmin, isLocalDevEnv, isOpenSourceEdition } from 'app/features/alerti import { ConnectionsRedirectNotice } from 'app/features/connections/components/ConnectionsRedirectNotice'; import { ROUTES as CONNECTIONS_ROUTES } from 'app/features/connections/constants'; import { getRoutes as getDataConnectionsRoutes } from 'app/features/connections/routes'; +import { DASHBOARD_LIBRARY_ROUTES } from 'app/features/dashboard/dashgrid/types'; import { DATASOURCES_ROUTES } from 'app/features/datasources/constants'; import { ConfigureIRM } from 'app/features/gops/configuration-tracker/components/ConfigureIRM'; import { getRoutes as getPluginCatalogRoutes } from 'app/features/plugins/admin/routes'; @@ -65,6 +66,15 @@ export function getAppRoutes(): RouteDescriptor[] { () => import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/NewDashboardWithDS') ), }, + { + path: DASHBOARD_LIBRARY_ROUTES.Template, + roles: () => contextSrv.evaluatePermission([AccessControlAction.DashboardsCreate]), + pageClass: 'page-dashboard', + routeName: DashboardRoutes.Template, + component: SafeDynamicImport( + () => import(/* webpackChunkName: "DashboardPage" */ '../features/dashboard/containers/DashboardPageProxy') + ), + }, { path: '/dashboard/:type/:slug', pageClass: 'page-dashboard', diff --git a/public/app/types/dashboard.ts b/public/app/types/dashboard.ts index a5705803382..4b926a7a5db 100644 --- a/public/app/types/dashboard.ts +++ b/public/app/types/dashboard.ts @@ -108,6 +108,7 @@ export interface DashboardDataDTO extends Dashboard { export enum DashboardRoutes { Home = 'home-dashboard', New = 'new-dashboard', + Template = 'template-dashboard', Normal = 'normal-dashboard', Provisioning = 'provisioning-dashboard', Scripted = 'scripted-dashboard', diff --git a/public/img/dashboard-library/dashboard_library_1.jpg b/public/img/dashboard-library/dashboard_library_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d4d4b508cea00c95aad6461c1c87be75485d077f GIT binary patch literal 103917 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 literal 0 HcmV?d00001 diff --git a/public/img/dashboard-library/dashboard_library_2.jpg b/public/img/dashboard-library/dashboard_library_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5f40e9ae2f86ec7ce9dff2aabfb8fe869ce5be5f GIT binary patch literal 113221 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 Sli-9mhu!oWPrvrh*#7~EnrnXm literal 0 HcmV?d00001 diff --git a/public/img/dashboard-library/dashboard_library_3.jpg b/public/img/dashboard-library/dashboard_library_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5b665f352a5ef02765fba673753e7c007be6def2 GIT binary patch literal 112953 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 >4<dXG4pN2hT(V@P*s}emkW7iY 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 E1F<kC$p8QV literal 0 HcmV?d00001 diff --git a/public/img/dashboard-library/dashboard_library_4.jpg b/public/img/dashboard-library/dashboard_library_4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d04619868bc949b0a434405c4c7548039e7c5bb5 GIT binary patch literal 92639 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 literal 0 HcmV?d00001 diff --git a/public/img/dashboard-library/dashboard_library_5.jpg b/public/img/dashboard-library/dashboard_library_5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ed479bd01c893290c5a66dfde1c5eba6b4ac2b2f GIT binary patch literal 84785 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 )Y*`BSvym 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 literal 0 HcmV?d00001 diff --git a/public/img/dashboard-library/dashboard_library_6.jpg b/public/img/dashboard-library/dashboard_library_6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4cdfb7914f237657aa78679ff47e14790c31d999 GIT binary patch literal 111876 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ඎXF+(^T3IvWi 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 Ii%UTN*>g6`;Q#;t literal 0 HcmV?d00001 diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index df3dc82617e..9c91547b203 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -4829,7 +4829,11 @@ "add-visualization-header": "Start your new dashboard by adding a visualization", "import-a-dashboard-body": "Import dashboards from files or <2>grafana.com</2>.", "import-a-dashboard-header": "Import a dashboard", - "import-dashboard-button": "Import dashboard" + "import-dashboard-button": "Import dashboard", + "show-less-dashboards": "Show less", + "show-more-dashboards": "Show more", + "start-with-suggested-dashboards": "Start with a pre-made dashboard from your data source", + "use-template-button": "Use this dashboard" }, "errors": { "failed-to-load": "Failed to load dashboard" diff --git a/public/openapi3.json b/public/openapi3.json index 82afc3051fc..162af40b01c 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -1405,6 +1405,14 @@ }, "description": "InternalServerPublicError is a general error indicating something went wrong internally." }, + "interpolateDashboardResponse": { + "content": { + "application/json": { + "schema": {} + } + }, + "description": "(empty)" + }, "jwksResponse": { "content": { "application/json": { @@ -17742,6 +17750,32 @@ ] } }, + "/dashboards/interpolate": { + "post": { + "operationId": "interpolateDashboard", + "responses": { + "200": { + "$ref": "#/components/responses/interpolateDashboardResponse" + }, + "400": { + "$ref": "#/components/responses/badRequestError" + }, + "401": { + "$ref": "#/components/responses/unauthorisedError" + }, + "422": { + "$ref": "#/components/responses/unprocessableEntityError" + }, + "500": { + "$ref": "#/components/responses/internalServerError" + } + }, + "summary": "Interpolate dashboard. This is an experimental endpoint under dashboardLibrary FF and is subject to change.", + "tags": [ + "dashboards" + ] + } + }, "/dashboards/public-dashboards": { "get": { "description": "Get list of public dashboards", From 6baa257f91b4b0d2d2cd4ebc6b1b7e194e7504bd Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:18:50 +0200 Subject: [PATCH 31/75] Alerting: Remove __grafana_origin when duplicating rule (#112396) * remove __grafana_origin when duplicating rule * Fix origin removal condition to handle cloud rules * Improve origin label condition, improve tests * Fix import path --------- Co-authored-by: Konrad Lalik <konradlalik@gmail.com> --- .../rule-editor/CloneRuleEditor.test.tsx | 138 +-------- .../unified/rule-editor/clone.utils.test.tsx | 261 ++++++++++++++++++ .../unified/rule-editor/clone.utils.ts | 8 +- .../features/alerting/unified/utils/rules.ts | 1 + 4 files changed, 270 insertions(+), 138 deletions(-) create mode 100644 public/app/features/alerting/unified/rule-editor/clone.utils.test.tsx diff --git a/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx index 8b31e3a946a..2572d9ddfc5 100644 --- a/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx @@ -6,24 +6,10 @@ import { byRole, byTestId } from 'testing-library-selector'; import { MIMIR_DATASOURCE_UID } from 'app/features/alerting/unified/mocks/server/constants'; import { DashboardSearchItemType } from 'app/features/search/types'; import { AccessControlAction } from 'app/types/accessControl'; -import { RuleWithLocation } from 'app/types/unified-alerting'; -import { - RulerAlertingRuleDTO, - RulerGrafanaRuleDTO, - RulerRecordingRuleDTO, - RulerRuleDTO, -} from 'app/types/unified-alerting-dto'; import { ExpressionEditorProps } from '../components/rule-editor/ExpressionEditor'; import { setupMswServer } from '../mockApi'; -import { - grantUserPermissions, - mockDataSource, - mockFolder, - mockRulerAlertingRule, - mockRulerGrafanaRule, - mockRulerRuleGroup, -} from '../mocks'; +import { grantUserPermissions, mockDataSource, mockFolder, mockRulerAlertingRule } from '../mocks'; import { grafanaRulerRule, mockPreviewApiResponse } from '../mocks/grafanaRulerApi'; import { mockRulerRulesApiResponse, mockRulerRulesGroupApiResponse } from '../mocks/rulerApi'; import { setFolderResponse } from '../mocks/server/configure'; @@ -34,7 +20,6 @@ import { Annotation } from '../utils/constants'; import { hashRulerRule } from '../utils/rule-id'; import { ExistingRuleEditor } from './ExistingRuleEditor'; -import { cloneRuleDefinition } from './clone.utils'; import { getDefaultFormValues } from './formDefaults'; jest.mock('../components/rule-editor/ExpressionEditor', () => ({ @@ -198,125 +183,4 @@ describe('CloneRuleEditor', function () { expect(ui.inputs.annotationValue(0).get()).toHaveTextContent('This is a very important alert rule'); }); }); - - describe('cloneRuleDefinition', () => { - it("Should change the cloned rule's name accordingly for Grafana rules", () => { - const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( - { - for: '1m', - labels: { severity: 'critical', region: 'nasa' }, - annotations: { [Annotation.summary]: 'This is a very important alert rule' }, - }, - { uid: 'grafana-rule-1', title: 'First Grafana Rule', data: [] } - ); - - const originalRule: RuleWithLocation<RulerGrafanaRuleDTO> = { - ruleSourceName: 'my-prom-ds', - namespace: 'namespace-one', - group: mockRulerRuleGroup(), - rule, - }; - - const clonedRule: RuleWithLocation<RulerRuleDTO> = cloneRuleDefinition(originalRule); - - const grafanaRule: RulerGrafanaRuleDTO = clonedRule.rule as RulerGrafanaRuleDTO; - - expect(originalRule.rule.grafana_alert.title).toEqual('First Grafana Rule'); - expect(grafanaRule.grafana_alert.title).toEqual('First Grafana Rule (copy)'); - }); - - it("Should change the cloned rule's name accordingly for Ruler rules", () => { - const rule: RulerAlertingRuleDTO = mockRulerAlertingRule({ - for: '1m', - alert: 'First Ruler Rule', - expr: 'vector(1) > 0', - labels: { severity: 'critical', region: 'nasa' }, - annotations: { [Annotation.summary]: 'This is a very important alert rule' }, - }); - - const originalRule: RuleWithLocation<RulerAlertingRuleDTO> = { - ruleSourceName: 'my-prom-ds', - namespace: 'namespace-one', - group: mockRulerRuleGroup(), - rule, - }; - - const clonedRule: RuleWithLocation<RulerRuleDTO> = cloneRuleDefinition(originalRule); - - const alertingRule: RulerAlertingRuleDTO = clonedRule.rule as RulerAlertingRuleDTO; - - expect(originalRule.rule.alert).toEqual('First Ruler Rule'); - expect(alertingRule.alert).toEqual('First Ruler Rule (copy)'); - }); - - it("Should change the cloned rule's name accordingly for Recording rules", () => { - const rule: RulerRecordingRuleDTO = { - record: 'instance:node_num_cpu:sum', - expr: 'count without (cpu) (count without (mode) (node_cpu_seconds_total{job="integrations/node_exporter"}))', - labels: { type: 'cpu' }, - }; - - const originalRule: RuleWithLocation<RulerRecordingRuleDTO> = { - ruleSourceName: 'my-prom-ds', - namespace: 'namespace-one', - group: mockRulerRuleGroup(), - rule, - }; - - const clonedRule: RuleWithLocation<RulerRuleDTO> = cloneRuleDefinition(originalRule); - - const recordingRule: RulerRecordingRuleDTO = clonedRule.rule as RulerRecordingRuleDTO; - - expect(originalRule.rule.record).toEqual('instance:node_num_cpu:sum'); - expect(recordingRule.record).toEqual('instance:node_num_cpu:sum (copy)'); - }); - - it('Should remove the group for provisioned Grafana rules', () => { - const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( - { - for: '1m', - labels: { severity: 'critical', region: 'nasa' }, - annotations: { [Annotation.summary]: 'This is a very important alert rule' }, - }, - { uid: 'grafana-rule-1', title: 'First Grafana Rule', data: [], provenance: 'foo' } - ); - - const originalRule: RuleWithLocation<RulerGrafanaRuleDTO> = { - ruleSourceName: 'my-prom-ds', - namespace: 'namespace-one', - group: mockRulerRuleGroup(), - rule, - }; - - const clonedRule: RuleWithLocation<RulerRuleDTO> = cloneRuleDefinition(originalRule); - - expect(originalRule.group.name).toEqual('group1'); - expect(clonedRule.group.name).toEqual(''); - }); - - it('The cloned rule should not contain a UID property', () => { - const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( - { - for: '1m', - labels: { severity: 'critical', region: 'nasa' }, - annotations: { [Annotation.summary]: 'This is a very important alert rule' }, - }, - { uid: 'grafana-rule-1', title: 'First Grafana Rule', data: [] } - ); - - const originalRule: RuleWithLocation<RulerGrafanaRuleDTO> = { - ruleSourceName: 'my-prom-ds', - namespace: 'namespace-one', - group: mockRulerRuleGroup(), - rule, - }; - - const clonedRule: RuleWithLocation<RulerRuleDTO> = cloneRuleDefinition(originalRule); - - const grafanaRule: RulerGrafanaRuleDTO = clonedRule.rule as RulerGrafanaRuleDTO; - - expect(originalRule.rule.grafana_alert.uid).toEqual('grafana-rule-1'); - expect(grafanaRule.grafana_alert.uid).toEqual(''); - }); - }); }); diff --git a/public/app/features/alerting/unified/rule-editor/clone.utils.test.tsx b/public/app/features/alerting/unified/rule-editor/clone.utils.test.tsx new file mode 100644 index 00000000000..ff2f8f945db --- /dev/null +++ b/public/app/features/alerting/unified/rule-editor/clone.utils.test.tsx @@ -0,0 +1,261 @@ +import { config } from '@grafana/runtime'; +import { RuleWithLocation } from 'app/types/unified-alerting'; +import { + RulerAlertingRuleDTO, + RulerGrafanaRuleDTO, + RulerRecordingRuleDTO, + RulerRuleDTO, +} from 'app/types/unified-alerting-dto'; + +import { mockRulerAlertingRule, mockRulerGrafanaRule, mockRulerRuleGroup } from '../mocks'; +import { pluginMeta, pluginMetaToPluginConfig } from '../testSetup/plugins'; +import { SupportedPlugin } from '../types/pluginBridges'; +import { Annotation } from '../utils/constants'; +import { GRAFANA_ORIGIN_LABEL } from '../utils/labels'; + +import { cloneRuleDefinition } from './clone.utils'; + +describe('cloneRuleDefinition', () => { + it("Should change the cloned rule's name accordingly for Grafana rules", () => { + const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( + { + for: '1m', + labels: { severity: 'critical', region: 'nasa' }, + annotations: { [Annotation.summary]: 'This is a very important alert rule' }, + }, + { uid: 'grafana-rule-1', title: 'First Grafana Rule', data: [] } + ); + + const originalRule: RuleWithLocation<RulerGrafanaRuleDTO> = { + ruleSourceName: 'my-prom-ds', + namespace: 'namespace-one', + group: mockRulerRuleGroup(), + rule, + }; + + const clonedRule: RuleWithLocation<RulerRuleDTO> = cloneRuleDefinition(originalRule); + + const grafanaRule: RulerGrafanaRuleDTO = clonedRule.rule as RulerGrafanaRuleDTO; + + expect(originalRule.rule.grafana_alert.title).toEqual('First Grafana Rule'); + expect(grafanaRule.grafana_alert.title).toEqual('First Grafana Rule (copy)'); + }); + + it("Should change the cloned rule's name accordingly for Ruler rules", () => { + const rule: RulerAlertingRuleDTO = mockRulerAlertingRule({ + for: '1m', + alert: 'First Ruler Rule', + expr: 'vector(1) > 0', + labels: { severity: 'critical', region: 'nasa' }, + annotations: { [Annotation.summary]: 'This is a very important alert rule' }, + }); + + const originalRule: RuleWithLocation<RulerAlertingRuleDTO> = { + ruleSourceName: 'my-prom-ds', + namespace: 'namespace-one', + group: mockRulerRuleGroup(), + rule, + }; + + const clonedRule: RuleWithLocation<RulerRuleDTO> = cloneRuleDefinition(originalRule); + + const alertingRule: RulerAlertingRuleDTO = clonedRule.rule as RulerAlertingRuleDTO; + + expect(originalRule.rule.alert).toEqual('First Ruler Rule'); + expect(alertingRule.alert).toEqual('First Ruler Rule (copy)'); + }); + + it("Should change the cloned rule's name accordingly for Recording rules", () => { + const rule: RulerRecordingRuleDTO = { + record: 'instance:node_num_cpu:sum', + expr: 'count without (cpu) (count without (mode) (node_cpu_seconds_total{job="integrations/node_exporter"}))', + labels: { type: 'cpu' }, + }; + + const originalRule: RuleWithLocation<RulerRecordingRuleDTO> = { + ruleSourceName: 'my-prom-ds', + namespace: 'namespace-one', + group: mockRulerRuleGroup(), + rule, + }; + + const clonedRule: RuleWithLocation<RulerRuleDTO> = cloneRuleDefinition(originalRule); + + const recordingRule: RulerRecordingRuleDTO = clonedRule.rule as RulerRecordingRuleDTO; + + expect(originalRule.rule.record).toEqual('instance:node_num_cpu:sum'); + expect(recordingRule.record).toEqual('instance:node_num_cpu:sum (copy)'); + }); + + it('Should remove the group for provisioned Grafana rules', () => { + const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( + { + for: '1m', + labels: { severity: 'critical', region: 'nasa' }, + annotations: { [Annotation.summary]: 'This is a very important alert rule' }, + }, + { uid: 'grafana-rule-1', title: 'First Grafana Rule', data: [], provenance: 'foo' } + ); + + const originalRule: RuleWithLocation<RulerGrafanaRuleDTO> = { + ruleSourceName: 'my-prom-ds', + namespace: 'namespace-one', + group: mockRulerRuleGroup(), + rule, + }; + + const clonedRule: RuleWithLocation<RulerRuleDTO> = cloneRuleDefinition(originalRule); + + expect(originalRule.group.name).toEqual('group1'); + expect(clonedRule.group.name).toEqual(''); + }); + + it('The cloned rule should not contain a UID property', () => { + const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( + { + for: '1m', + labels: { severity: 'critical', region: 'nasa' }, + annotations: { [Annotation.summary]: 'This is a very important alert rule' }, + }, + { uid: 'grafana-rule-1', title: 'First Grafana Rule', data: [] } + ); + + const originalRule: RuleWithLocation<RulerGrafanaRuleDTO> = { + ruleSourceName: 'my-prom-ds', + namespace: 'namespace-one', + group: mockRulerRuleGroup(), + rule, + }; + + const clonedRule: RuleWithLocation<RulerRuleDTO> = cloneRuleDefinition(originalRule); + + const grafanaRule: RulerGrafanaRuleDTO = clonedRule.rule as RulerGrafanaRuleDTO; + + expect(originalRule.rule.grafana_alert.uid).toEqual('grafana-rule-1'); + expect(grafanaRule.grafana_alert.uid).toEqual(''); + }); + + it('Should remove the origin label when cloning data source plugin-provided rules', () => { + // Mock the plugin as installed + config.apps = { + [SupportedPlugin.Slo]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Slo]), + }; + + const rule: RulerAlertingRuleDTO = mockRulerAlertingRule({ + alert: 'slo-provider-alert', + expr: 'vector(1) > 0', + for: '1m', + labels: { + severity: 'critical', + region: 'nasa', + [GRAFANA_ORIGIN_LABEL]: 'plugin/' + SupportedPlugin.Slo, + }, + annotations: { [Annotation.summary]: 'This is a plugin-provided alert rule' }, + }); + + const originalRule: RuleWithLocation<RulerAlertingRuleDTO> = { + ruleSourceName: 'my-prom-ds', + namespace: 'namespace-one', + group: mockRulerRuleGroup(), + rule, + }; + + const { rule: clonedRule } = cloneRuleDefinition(originalRule); + + // Original rule should have the origin label + expect(originalRule.rule.labels?.[GRAFANA_ORIGIN_LABEL]).toEqual('plugin/' + SupportedPlugin.Slo); + + // Cloned rule should not have the origin label + expect(clonedRule.labels?.[GRAFANA_ORIGIN_LABEL]).toBeUndefined(); + + // Other labels should be preserved + expect(clonedRule.labels?.severity).toEqual('critical'); + expect(clonedRule.labels?.region).toEqual('nasa'); + }); + + it('Should remove the origin label when cloning Grafana-managed plugin-provided rules', () => { + config.apps = { + [SupportedPlugin.Slo]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Slo]), + }; + + const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( + { + for: '1m', + labels: { + severity: 'critical', + [GRAFANA_ORIGIN_LABEL]: 'plugin/' + SupportedPlugin.Slo, + }, + annotations: { [Annotation.summary]: 'Plugin-provided Grafana rule' }, + }, + { uid: 'grafana-plugin-rule', title: 'Plugin Grafana Rule', data: [] } + ); + + const originalRule: RuleWithLocation<RulerGrafanaRuleDTO> = { + ruleSourceName: 'grafana', + namespace: 'namespace-one', + group: mockRulerRuleGroup(), + rule, + }; + + const { rule: clonedRule } = cloneRuleDefinition(originalRule); + + expect(originalRule.rule.labels?.[GRAFANA_ORIGIN_LABEL]).toEqual('plugin/' + SupportedPlugin.Slo); + expect(clonedRule.labels?.[GRAFANA_ORIGIN_LABEL]).toBeUndefined(); + expect(clonedRule.labels?.severity).toEqual('critical'); + }); + + it('Should remove the origin label when cloning data source recording rules', () => { + const rule: RulerRecordingRuleDTO = { + record: 'plugin:recording:rule', + expr: 'sum(metric)', + labels: { + type: 'cpu', + [GRAFANA_ORIGIN_LABEL]: 'plugin/' + SupportedPlugin.Slo, + }, + }; + + const originalRule: RuleWithLocation<RulerRecordingRuleDTO> = { + ruleSourceName: 'my-prom-ds', + namespace: 'namespace-one', + group: mockRulerRuleGroup(), + rule, + }; + + const { rule: clonedRule } = cloneRuleDefinition(originalRule); + + expect(originalRule.rule.labels?.[GRAFANA_ORIGIN_LABEL]).toBeDefined(); + expect(clonedRule.labels?.[GRAFANA_ORIGIN_LABEL]).toBeUndefined(); + expect(clonedRule.labels?.type).toEqual('cpu'); + }); + + it('Should preserve all labels when cloning non-plugin-provided rules', () => { + const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule( + { + for: '1m', + labels: { + severity: 'critical', + region: 'nasa', + custom_label: 'custom_value', + }, + annotations: { [Annotation.summary]: 'This is a regular alert rule' }, + }, + { uid: 'regular-rule-1', title: 'Regular Alert Rule', data: [] } + ); + + const originalRule: RuleWithLocation<RulerGrafanaRuleDTO> = { + ruleSourceName: 'my-prom-ds', + namespace: 'namespace-one', + group: mockRulerRuleGroup(), + rule, + }; + + const clonedRule: RuleWithLocation<RulerRuleDTO> = cloneRuleDefinition(originalRule); + + const grafanaRule: RulerGrafanaRuleDTO = clonedRule.rule as RulerGrafanaRuleDTO; + + // All labels should be preserved for non-plugin rules + expect(grafanaRule.labels?.severity).toEqual('critical'); + expect(grafanaRule.labels?.region).toEqual('nasa'); + expect(grafanaRule.labels?.custom_label).toEqual('custom_value'); + }); +}); diff --git a/public/app/features/alerting/unified/rule-editor/clone.utils.ts b/public/app/features/alerting/unified/rule-editor/clone.utils.ts index a4b38a3b7a7..f109866ef61 100644 --- a/public/app/features/alerting/unified/rule-editor/clone.utils.ts +++ b/public/app/features/alerting/unified/rule-editor/clone.utils.ts @@ -4,7 +4,8 @@ import { RuleWithLocation } from 'app/types/unified-alerting'; import { RulerRuleDTO } from 'app/types/unified-alerting-dto'; import { generateCopiedName } from '../utils/duplicate'; -import { getRuleName, rulerRuleType } from '../utils/rules'; +import { GRAFANA_ORIGIN_LABEL } from '../utils/labels'; +import { getRuleName, isPluginProvidedRule, rulerRuleType } from '../utils/rules'; export function changeRuleName(rule: RulerRuleDTO, newName: string) { if (rulerRuleType.grafana.rule(rule)) { @@ -35,5 +36,10 @@ export function cloneRuleDefinition(rule: RuleWithLocation<RulerRuleDTO>) { } } + if (rulerRuleType.any.rule(ruleClone.rule) && isPluginProvidedRule(ruleClone.rule)) { + // Remove the origin label when cloning plugin-provided rules + delete ruleClone.rule.labels?.[GRAFANA_ORIGIN_LABEL]; + } + return ruleClone; } diff --git a/public/app/features/alerting/unified/utils/rules.ts b/public/app/features/alerting/unified/utils/rules.ts index 0778bcc2903..e3d35972be5 100644 --- a/public/app/features/alerting/unified/utils/rules.ts +++ b/public/app/features/alerting/unified/utils/rules.ts @@ -120,6 +120,7 @@ export const rulerRuleType = { recordingRule: isCloudRecordingRulerRule, }, any: { + rule: (rule?: RulerRuleDTO) => isCloudRulerRule(rule) || isGrafanaRulerRule(rule), recordingRule: (rule?: RulerRuleDTO) => isCloudRecordingRulerRule(rule) || isGrafanaRecordingRule(rule), alertingRule: (rule?: RulerRuleDTO) => isAlertingRulerRule(rule) || isGrafanaAlertingRule(rule), }, From d3cd4017345994cd8cef589379e9b872cfe63947 Mon Sep 17 00:00:00 2001 From: Ashley Harrison <ashley.harrison@grafana.com> Date: Wed, 15 Oct 2025 13:35:24 +0100 Subject: [PATCH 32/75] Frontend service: Add `/-/fe-boot-error` endpoint to track errors starting the frontend service (#112375) * add /-/fe-boot-error endpoint to track errors starting the frontend service * use promauto package * move metric to root * check error of r.Body.Close() --- pkg/services/frontend/frontend_service.go | 47 +++++++++++++++++++++++ pkg/services/frontend/index.html | 17 +++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/pkg/services/frontend/frontend_service.go b/pkg/services/frontend/frontend_service.go index 32cea9cffc8..d1e343dede7 100644 --- a/pkg/services/frontend/frontend_service.go +++ b/pkg/services/frontend/frontend_service.go @@ -2,11 +2,13 @@ package frontend import ( "context" + "io" "net" "net/http" "time" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" @@ -25,6 +27,14 @@ import ( var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/frontend") +// Initialize metrics +var bootErrorMetric = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: "grafana", + Subsystem: "frontend", + Name: "boot_errors_total", + Help: "Total number of frontend boot errors", +}) + type frontendService struct { *services.BasicService cfg *setting.Cfg @@ -117,6 +127,11 @@ func (s *frontendService) routeGet(m *web.Mux, pattern string, h ...web.Handler) m.Get(pattern, handlers...) } +func (s *frontendService) routePost(m *web.Mux, pattern string, h ...web.Handler) { + handlers := append([]web.Handler{middleware.ProvideRouteOperationName(pattern)}, h...) + m.Post(pattern, handlers...) +} + // Apply the same middleware patterns as the main HTTP server func (s *frontendService) addMiddlewares(m *web.Mux) { loggermiddleware := loggermw.Provide(s.cfg, s.features) @@ -148,6 +163,38 @@ func (s *frontendService) registerRoutes(m *web.Mux) { } }) + // Frontend boot error reporting endpoint + s.routePost(m, "/-/fe-boot-error", s.handleBootError) + // All other requests return index.html s.routeGet(m, "/*", s.index.HandleRequest) } + +// handleBootError handles frontend boot error reports +func (s *frontendService) handleBootError(w http.ResponseWriter, r *http.Request) { + // Read the request body + body, err := io.ReadAll(r.Body) + if err != nil { + s.log.Error("failed to read boot error request body", "error", err) + http.Error(w, "Bad request", http.StatusBadRequest) + return + } + + defer func() { + if err := r.Body.Close(); err != nil { + s.log.Warn("Failed to close response body", "err", err) + } + }() + + // Increment the Prometheus counter + bootErrorMetric.Inc() + + // Log the error details + s.log.Error("frontend boot error reported", "error", body) + + // Return success response + w.WriteHeader(http.StatusOK) + if _, err := w.Write([]byte("OK")); err != nil { + s.log.Error("failed to write boot error response", "error", err) + } +} diff --git a/pkg/services/frontend/index.html b/pkg/services/frontend/index.html index 957581dc56f..34c4d4f89ed 100644 --- a/pkg/services/frontend/index.html +++ b/pkg/services/frontend/index.html @@ -169,10 +169,23 @@ // Wrap in an IIFE to avoid polluting the global scope. Intentionally global-scope properties // are explicitly assigned to the `window` object. (() => { - window.__grafana_load_failed = function(...args) { - console.error('Failed to load Grafana', ...args); + window.__grafana_load_failed = function(err) { + console.error('Failed to load Grafana', err); document.querySelector('.fs-variant-loader').classList.add('fs-hidden'); document.querySelector('.fs-variant-error').classList.remove('fs-hidden'); + + // Report the error to the backend + const errorMessage = err ? err.message : 'Unknown error'; + + fetch('/-/fe-boot-error', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: errorMessage, + }).catch(err => { + console.error('Failed to report boot error to backend: ', err); + }); }; window.onload = function() { From 77b62714236d16ed33c222b39e4efb5ee9973323 Mon Sep 17 00:00:00 2001 From: Ashley Harrison <ashley.harrison@grafana.com> Date: Wed, 15 Oct 2025 13:35:44 +0100 Subject: [PATCH 33/75] Checkbox: Improve accessibility of the `indeterminate` state (#112388) use indeterminate instead of aria-checked --- eslint-suppressions.json | 5 ----- .../src/components/Forms/Checkbox.story.tsx | 4 +--- .../src/components/Forms/Checkbox.tsx | 22 ++++++++++++++----- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 237e55d979c..2ceaa530a83 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -696,11 +696,6 @@ "count": 3 } }, - "packages/grafana-ui/src/components/Forms/Checkbox.story.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "packages/grafana-ui/src/components/Forms/Form.story.tsx": { "react-hooks/rules-of-hooks": { "count": 9 diff --git a/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx b/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx index f79388911bd..f08f7a996af 100644 --- a/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx +++ b/packages/grafana-ui/src/components/Forms/Checkbox.story.tsx @@ -18,8 +18,6 @@ const meta: Meta<typeof Checkbox> = { controls: { exclude: ['value', 'htmlValue'], }, - // TODO fix a11y issue in story and remove this - a11y: { test: 'off' }, }, }; @@ -94,7 +92,7 @@ export const AllStates: StoryFn<typeof Checkbox> = (args) => { <Checkbox value={checked} onChange={onChange} {...args} /> <Checkbox value={true} label="Checked" /> <Checkbox value={false} label="Unchecked" /> - <Checkbox value={false} indeterminate={true} label="Interdeterminate" /> + <Checkbox value={false} indeterminate={true} label="Indeterminate" /> <Checkbox value={false} invalid={true} label="Invalid and unchecked" /> <Checkbox value={true} invalid={true} label="Invalid and checked" /> </Stack> diff --git a/packages/grafana-ui/src/components/Forms/Checkbox.tsx b/packages/grafana-ui/src/components/Forms/Checkbox.tsx index 29b8dc5e699..173f821e287 100644 --- a/packages/grafana-ui/src/components/Forms/Checkbox.tsx +++ b/packages/grafana-ui/src/components/Forms/Checkbox.tsx @@ -18,7 +18,7 @@ export interface CheckboxProps extends Omit<HTMLProps<HTMLInputElement>, 'value' value?: boolean; /** htmlValue allows to specify the input "value" attribute */ htmlValue?: string | number; - /** Sets the checkbox into a "mixed" state. This is only a visual change and does not affect the value. */ + /** Sets the checkbox into a "mixed" state */ indeterminate?: boolean; /** Show an invalid state around the input */ invalid?: boolean; @@ -39,8 +39,6 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>( ); const styles = useStyles2(getCheckboxStyles, invalid); - const ariaChecked = indeterminate ? 'mixed' : undefined; - return ( <label className={cx(styles.wrapper, className)}> <div className={styles.checkboxWrapper}> @@ -51,9 +49,21 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>( disabled={disabled} onChange={handleOnChange} value={htmlValue} - aria-checked={ariaChecked} {...inputProps} - ref={ref} + ref={(element) => { + if (element && indeterminate) { + element.indeterminate = true; + } + + // we have to manually assign the ref since we need to modify the indeterminate property + if (ref) { + if (typeof ref === 'function') { + ref(element); + } else { + ref.current = element; + } + } + }} /> <span className={styles.checkmark} /> </div> @@ -139,7 +149,7 @@ export const getCheckboxStyles = (theme: GrafanaTheme2, invalid = false) => { }), inputIndeterminate: css({ - "&[aria-checked='mixed'] + span": { + '&:indeterminate + span': { border: `1px solid ${getBorderColor(theme.colors.primary.main)}`, background: theme.colors.primary.main, From 7e6d2514ef54abe2c11ca66c3260b613933e3da7 Mon Sep 17 00:00:00 2001 From: Ashley Harrison <ashley.harrison@grafana.com> Date: Wed, 15 Oct 2025 13:35:55 +0100 Subject: [PATCH 34/75] Storybook: Ignore storybook body when checking scrollable regions a11y (#112423) ignore storybook body when considering if scrollable regions are focusable --- eslint-suppressions.json | 5 ----- packages/grafana-ui/.storybook/preview.ts | 13 ++++++++++++- .../src/components/Layout/Stack/Stack.story.tsx | 2 -- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 2ceaa530a83..0fecfa471ed 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -785,11 +785,6 @@ "count": 2 } }, - "packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "packages/grafana-ui/src/components/List/AbstractList.tsx": { "react-prefer-function-component/react-prefer-function-component": { "count": 1 diff --git a/packages/grafana-ui/.storybook/preview.ts b/packages/grafana-ui/.storybook/preview.ts index 124981e1434..e245a46646a 100644 --- a/packages/grafana-ui/.storybook/preview.ts +++ b/packages/grafana-ui/.storybook/preview.ts @@ -64,7 +64,18 @@ const preview: Preview = { docs: { container: ThemedDocsContainer, }, - a11y: { test: 'error' }, + a11y: { + test: 'error', + config: { + rules: [ + { + id: 'scrollable-region-focusable', + selector: 'body', + enabled: false, + }, + ], + }, + }, knobs: { disable: true, }, diff --git a/packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx b/packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx index 14e4e3b5124..5acad6ab10f 100644 --- a/packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx +++ b/packages/grafana-ui/src/components/Layout/Stack/Stack.story.tsx @@ -16,8 +16,6 @@ const meta: Meta<typeof Stack> = { docs: { page: mdx, }, - // TODO fix a11y issue in story and remove this - a11y: { test: 'off' }, }, }; From 2296474f738eb7909ae84f65c89fcf72f32a45b7 Mon Sep 17 00:00:00 2001 From: Ashley Harrison <ashley.harrison@grafana.com> Date: Wed, 15 Oct 2025 13:44:51 +0100 Subject: [PATCH 35/75] Extensions sidebar: Fix remounting of all global styles on open/close (#112321) don't rerender all global styles when extension sidebar is opened --- .../src/themes/GlobalStyles/GlobalStyles.tsx | 9 ++------- .../grafana-ui/src/themes/GlobalStyles/elements.ts | 14 ++++++-------- public/app/AppWrapper.tsx | 5 ++--- .../ExtensionSidebar/ExtensionSidebar.tsx | 6 ++++++ .../ExtensionSidebar/GlobalStylesWrapper.tsx | 13 ------------- 5 files changed, 16 insertions(+), 31 deletions(-) delete mode 100644 public/app/core/components/AppChrome/ExtensionSidebar/GlobalStylesWrapper.tsx diff --git a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx index ad4be373aca..c0021ba103b 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx +++ b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx @@ -24,14 +24,9 @@ import { getSlateStyles } from './slate'; import { getUplotStyles } from './uPlot'; import { getUtilityClassStyles } from './utilityClasses'; -interface GlobalStylesProps { - isExtensionSidebarOpen?: boolean; -} - /** @internal */ -export function GlobalStyles(props: GlobalStylesProps) { +export function GlobalStyles() { const theme = useTheme2(); - const { isExtensionSidebarOpen } = props; return ( <Global @@ -41,7 +36,7 @@ export function GlobalStyles(props: GlobalStylesProps) { getCodeStyles(theme), getDashDiffStyles(theme), getDashboardGridStyles(theme), - getElementStyles(theme, isExtensionSidebarOpen), + getElementStyles(theme), getExtraStyles(theme), getFilterTableStyles(theme), getFontStyles(theme), diff --git a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts index 998b9421610..ea2b0945677 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts @@ -5,13 +5,7 @@ import { GrafanaTheme2, ThemeTypographyVariant } from '@grafana/data'; import { getFeatureToggle } from '../../utils/featureToggle'; import { getFocusStyles } from '../mixins'; -export function getElementStyles(theme: GrafanaTheme2, isExtensionSidebarOpen?: boolean) { - // in case the sidebar is closed, we want the body to scroll - // react select tries prevent scrolling by setting overflow/padding-right on the body - // Need type assertion here due to the use of !important - // see https://github.com/frenic/csstype/issues/114#issuecomment-697201978 - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const bodyOverflow = isExtensionSidebarOpen ? {} : { overflowY: 'auto !important' as 'auto' }; +export function getElementStyles(theme: GrafanaTheme2) { return css({ '*, *::before, *::after': { boxSizing: 'inherit', @@ -56,12 +50,16 @@ export function getElementStyles(theme: GrafanaTheme2, isExtensionSidebarOpen?: size: 'auto', padding: 0, }, + // react select tries prevent scrolling by setting overflow/padding-right on the body + // Need type assertion here due to the use of !important + // see https://github.com/frenic/csstype/issues/114#issuecomment-697201978 + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + overflowY: 'auto !important' as 'auto', // disable contextual font ligatures. otherwise, in firefox and safari, // an "x" between 2 numbers is replaced by a multiplication ligature // see https://github.com/rsms/inter/issues/222 fontVariantLigatures: 'no-contextual', ...theme.typography.body, - ...bodyOverflow, fontVariantNumeric: getFeatureToggle('tabularNumbers') ? 'tabular-nums' : 'initial', }, diff --git a/public/app/AppWrapper.tsx b/public/app/AppWrapper.tsx index d1c4eaf0db3..d7e87d7f5c3 100644 --- a/public/app/AppWrapper.tsx +++ b/public/app/AppWrapper.tsx @@ -6,13 +6,12 @@ import { Provider } from 'react-redux'; import { Route, Routes } from 'react-router-dom-v5-compat'; import { config, navigationLogger, reportInteraction } from '@grafana/runtime'; -import { ErrorBoundaryAlert, getPortalContainer, PortalContainer, TimeRangeProvider } from '@grafana/ui'; +import { ErrorBoundaryAlert, getPortalContainer, GlobalStyles, PortalContainer, TimeRangeProvider } from '@grafana/ui'; import { getAppRoutes } from 'app/routes/routes'; import { store } from 'app/store/store'; import { GrafanaApp } from './app'; import { ExtensionSidebarContextProvider } from './core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider'; -import { GlobalStylesWrapper } from './core/components/AppChrome/ExtensionSidebar/GlobalStylesWrapper'; import { GrafanaContext } from './core/context/GrafanaContext'; import { GrafanaRouteWrapper } from './core/navigation/GrafanaRoute'; import { RouteDescriptor } from './core/navigation/types'; @@ -121,7 +120,7 @@ export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> { <ExtensionRegistriesProvider registries={pluginExtensionRegistries}> <ExtensionSidebarContextProvider> <UNSAFE_PortalProvider getContainer={getPortalContainer}> - <GlobalStylesWrapper /> + <GlobalStyles /> <div className="grafana-app"> <RouterWrapper {...routerWrapperProps} /> <LiveConnectionWarning /> diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx index 11ab8b354f0..1b632bdfcbf 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebar.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; +import { css as cssReact, Global } from '@emotion/react'; import { GrafanaTheme2, PluginExtensionPoints } from '@grafana/data'; import { usePluginComponents } from '@grafana/runtime'; @@ -41,6 +42,11 @@ export function ExtensionSidebar() { return ( <div className={styles.sidebarWrapper}> <div className={styles.content}> + {/* When the sidebar is open, we don't want the body to scroll */} + {/* Need type assertion here due to the use of !important */} + {/* see https://github.com/frenic/csstype/issues/114#issuecomment-697201978 */} + {/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */} + <Global styles={[cssReact({ body: { overflowY: 'unset !important' as 'unset' } })]} /> <ExtensionComponent {...props} /> </div> </div> diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/GlobalStylesWrapper.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/GlobalStylesWrapper.tsx deleted file mode 100644 index ae2d8a24266..00000000000 --- a/public/app/core/components/AppChrome/ExtensionSidebar/GlobalStylesWrapper.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { GlobalStyles } from '@grafana/ui'; - -import { useExtensionSidebarContext } from './ExtensionSidebarProvider'; - -/** - * This component is used to wrap the GlobalStyles component and pass the isExtensionSidebarOpen prop to it. - * Since GlobalStyles is imported from @grafana/ui, we need to wrap it in a component to use the useExtensionSidebarContext hook. - */ -export const GlobalStylesWrapper = () => { - const { isOpen } = useExtensionSidebarContext(); - - return <GlobalStyles isExtensionSidebarOpen={isOpen} />; -}; From 5c9dd9b06845387f6f57e0cbcfa2fd385783985c Mon Sep 17 00:00:00 2001 From: Ieva <ieva.vasiljeva@grafana.com> Date: Wed, 15 Oct 2025 15:34:19 +0100 Subject: [PATCH 36/75] AuthZ service: Correctly evaluate action sets for dashboard creation (#112425) correctly evaluate dash creation action sets --- pkg/services/authz/rbac/mapper.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/services/authz/rbac/mapper.go b/pkg/services/authz/rbac/mapper.go index cdbe021b315..42bac84c6da 100644 --- a/pkg/services/authz/rbac/mapper.go +++ b/pkg/services/authz/rbac/mapper.go @@ -124,6 +124,13 @@ func newDashboardTranslation() translation { actionSetMapping := make(map[string][]string) for verb, rbacAction := range dashTranslation.verbMapping { var dashActionSets []string + + // Dashboard creation is only part of folder action sets, so we handle it separately + if rbacAction == "dashboards:create" { + dashActionSets = append(dashActionSets, "folders:edit") + dashActionSets = append(dashActionSets, "folders:admin") + } + if slices.Contains(ossaccesscontrol.DashboardViewActions, rbacAction) { dashActionSets = append(dashActionSets, "dashboards:view") dashActionSets = append(dashActionSets, "folders:view") From 75a9c8c7058ae93e16c0bb07a2942406468b9a17 Mon Sep 17 00:00:00 2001 From: Tania <10127682+undef1nd@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:41:43 +0200 Subject: [PATCH 37/75] OpenFeature: Add nil check for URL when creating provider (#112435) --- pkg/services/featuremgmt/openfeature.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/featuremgmt/openfeature.go b/pkg/services/featuremgmt/openfeature.go index 001809c9e56..90aad955db1 100644 --- a/pkg/services/featuremgmt/openfeature.go +++ b/pkg/services/featuremgmt/openfeature.go @@ -77,7 +77,7 @@ func createProvider( return newStaticProvider(staticFlags) } - if u.String() == "" { + if u == nil || u.String() == "" { return nil, fmt.Errorf("feature provider url is required for GOFFProviderType") } From b80c3e6760d980334da884069a89ab2f8713aa37 Mon Sep 17 00:00:00 2001 From: Ashley Harrison <ashley.harrison@grafana.com> Date: Wed, 15 Oct 2025 16:51:44 +0100 Subject: [PATCH 38/75] Frontend service: Pass query params to `/bootdata` call (#112448) pass query params to /bootdata call --- pkg/services/frontend/index.html | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/services/frontend/index.html b/pkg/services/frontend/index.html index 34c4d4f89ed..9f5b5d48dba 100644 --- a/pkg/services/frontend/index.html +++ b/pkg/services/frontend/index.html @@ -212,7 +212,16 @@ * Will return a rejected promise on unrecoverable errors. **/ async function fetchBootData() { - const resp = await fetch("/bootdata"); + const queryParams = new URLSearchParams(window.location.search); + + // pass the search params through to the bootdata request + // this allows for overriding the theme/language etc + const bootDataUrl = new URL('/bootdata', window.location.origin); + for (const [key, value] of queryParams.entries()) { + bootDataUrl.searchParams.append(key, value); + } + + const resp = await fetch(bootDataUrl); const textResponse = await resp.text(); let rawBootData; From ec8bb80a2461f8a61c0dc40c45f9a1a7b28a553f Mon Sep 17 00:00:00 2001 From: "renovate-sh-app[bot]" <219655108+renovate-sh-app[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 17:02:05 +0100 Subject: [PATCH 39/75] chore(deps): update dependency @formatjs/intl-durationformat to v0.7.6 (#112444) | datasource | package | from | to | | ---------- | ----------------------------- | ----- | ----- | | npm | @formatjs/intl-durationformat | 0.7.4 | 0.7.6 | Signed-off-by: renovate-sh-app[bot] <219655108+renovate-sh-app[bot]@users.noreply.github.com> Co-authored-by: renovate-sh-app[bot] <219655108+renovate-sh-app[bot]@users.noreply.github.com> --- yarn.lock | 62 +++++++++++++++---------------------------------------- 1 file changed, 17 insertions(+), 45 deletions(-) diff --git a/yarn.lock b/yarn.lock index 87d610fc52c..ab4d9f3cc1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2317,15 +2317,15 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.3.4": - version: 2.3.4 - resolution: "@formatjs/ecma402-abstract@npm:2.3.4" +"@formatjs/ecma402-abstract@npm:2.3.6": + version: 2.3.6 + resolution: "@formatjs/ecma402-abstract@npm:2.3.6" dependencies: "@formatjs/fast-memoize": "npm:2.2.7" - "@formatjs/intl-localematcher": "npm:0.6.1" + "@formatjs/intl-localematcher": "npm:0.6.2" decimal.js: "npm:^10.4.3" tslib: "npm:^2.8.0" - checksum: 10/573971ffc291096a4b9fcc80b4708124e89bf2e3ac50e0f78b41eb797e9aa1b842f4dc3665e4467a853c738386821769d9e40408a1d25bc73323a1f057a16cf2 + checksum: 10/30b1b5cd6b62ba46245f934429936592df5500bc1b089dc92dd49c826757b873dd92c305dcfe370701e4df6b057bf007782113abb9b65db550d73be4961718bc languageName: node linkType: hard @@ -2369,13 +2369,13 @@ __metadata: linkType: hard "@formatjs/intl-durationformat@npm:^0.7.0": - version: 0.7.4 - resolution: "@formatjs/intl-durationformat@npm:0.7.4" + version: 0.7.6 + resolution: "@formatjs/intl-durationformat@npm:0.7.6" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.4" - "@formatjs/intl-localematcher": "npm:0.6.1" + "@formatjs/ecma402-abstract": "npm:2.3.6" + "@formatjs/intl-localematcher": "npm:0.6.2" tslib: "npm:^2.8.0" - checksum: 10/d62273ecd635475ca91e9b501301f3f396403fa91b584c550734b19b2d194ba1316b27303fed985c1d42ae933d54eb220da6540edfdf376b0d9371ecfd0d4e15 + checksum: 10/442236ba85bcd9cb7296c43a708271fa09f110b1ca9d5899066d00812fc2965eaeaec6b5240be421b80daba62860352131088449ba0fcd2061f671cec6240f0b languageName: node linkType: hard @@ -2388,12 +2388,12 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.6.1": - version: 0.6.1 - resolution: "@formatjs/intl-localematcher@npm:0.6.1" +"@formatjs/intl-localematcher@npm:0.6.2": + version: 0.6.2 + resolution: "@formatjs/intl-localematcher@npm:0.6.2" dependencies: tslib: "npm:^2.8.0" - checksum: 10/c7b3bc8395d18670677f207b2fd107561fff5d6394a9b4273c29e0bea920300ec3a2eefead600ebb7761c04a770cada28f78ac059f84d00520bfb57a9db36998 + checksum: 10/eb12a7f5367bbecdfafc20d7f005559ce840f420e970f425c5213d35e94e86dfe75bde03464971a26494bf8427d4961269db22ecad2834f2a19d888b5d9cc064 languageName: node linkType: hard @@ -20495,14 +20495,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": - version: 3.2.0 - resolution: "istanbul-lib-coverage@npm:3.2.0" - checksum: 10/31621b84ad29339242b63d454243f558a7958ee0b5177749bacf1f74be7d95d3fd93853738ef7eebcddfaf3eab014716e51392a8dbd5aa1bdc1b15c2ebc53c24 - languageName: node - linkType: hard - -"istanbul-lib-coverage@npm:^3.2.2": +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0, istanbul-lib-coverage@npm:^3.2.2": version: 3.2.2 resolution: "istanbul-lib-coverage@npm:3.2.2" checksum: 10/40bbdd1e937dfd8c830fa286d0f665e81b7a78bdabcd4565f6d5667c99828bda3db7fb7ac6b96a3e2e8a2461ddbc5452d9f8bc7d00cb00075fa6a3e99f5b6a81 @@ -20570,18 +20563,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-report@npm:^3.0.0": - version: 3.0.0 - resolution: "istanbul-lib-report@npm:3.0.0" - dependencies: - istanbul-lib-coverage: "npm:^3.0.0" - make-dir: "npm:^3.0.0" - supports-color: "npm:^7.1.0" - checksum: 10/06b37952e9cb0fe419a37c7f3d74612a098167a9eb0e5264228036e78b42ca5226501e8130738b5306d94bae2ea068ca674080d4af959992523d84aacff67728 - languageName: node - linkType: hard - -"istanbul-lib-report@npm:^3.0.1": +"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": version: 3.0.1 resolution: "istanbul-lib-report@npm:3.0.1" dependencies: @@ -20603,17 +20585,7 @@ __metadata: languageName: node linkType: hard -"istanbul-reports@npm:^3.0.2, istanbul-reports@npm:^3.1.3": - version: 3.1.7 - resolution: "istanbul-reports@npm:3.1.7" - dependencies: - html-escaper: "npm:^2.0.0" - istanbul-lib-report: "npm:^3.0.0" - checksum: 10/f1faaa4684efaf57d64087776018d7426312a59aa6eeb4e0e3a777347d23cd286ad18f427e98f0e3dee666103d7404c9d7abc5f240406a912fa16bd6695437fa - languageName: node - linkType: hard - -"istanbul-reports@npm:^3.1.7": +"istanbul-reports@npm:^3.0.2, istanbul-reports@npm:^3.1.3, istanbul-reports@npm:^3.1.7": version: 3.2.0 resolution: "istanbul-reports@npm:3.2.0" dependencies: From 5b7e6ef2e1de14742a7064f8668328be28df06c5 Mon Sep 17 00:00:00 2001 From: Ashley Harrison <ashley.harrison@grafana.com> Date: Wed, 15 Oct 2025 17:07:34 +0100 Subject: [PATCH 40/75] Chore: ignore labels preset in renovate config (#112449) ignore labels preset in renovate config --- .github/renovate.json5 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 37d44d74483..aa33961bf95 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,6 +1,9 @@ { extends: ["config:recommended"], enabledManagers: ["npm"], + ignorePresets: [ + "github>grafana/grafana-renovate-config//presets/labels", + ], ignoreDeps: [ // ignoring these until we can upgrade to react 19 // see epic here: https://github.com/grafana/grafana/issues/98813 From d9067b27eb00ae3de8e0905add7ff09544aa6098 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:34:12 -0600 Subject: [PATCH 41/75] Dashboard Migrations: V2 - legacy services.filter and graphite panel type (#112282) * migrate to v2 * Fix tests * graphite panels should be auto-migrated * lint * Provisioning: Fix dashboard export to preserve original API version * Add error handling for the edge case where conversion fails but no storedVersion is available. --- .../pkg/migration/frontend_defaults.go | 61 +++-- .../pkg/migration/schemaversion/migrations.go | 3 +- .../pkg/migration/schemaversion/v2.go | 71 ++++++ .../pkg/migration/schemaversion/v2_test.go | 216 ++++++++++++++++++ .../input/v2.panels-and-services.json | 89 ++++++++ .../v2.panels-and-services.v42.json | 168 ++++++++++++++ .../v2.panels-and-services.v2.json | 121 ++++++++++ eslint-suppressions.json | 2 +- .../provisioning/jobs/export/resources.go | 15 +- .../dashboard/state/DashboardMigrator.ts | 48 +--- .../DashboardMigratorSingleVersion.test.ts | 6 +- .../state/DashboardMigratorToBackend.test.ts | 6 +- .../state/getPanelPluginToMigrateTo.ts | 6 +- 13 files changed, 734 insertions(+), 78 deletions(-) create mode 100644 apps/dashboard/pkg/migration/schemaversion/v2.go create mode 100644 apps/dashboard/pkg/migration/schemaversion/v2_test.go create mode 100644 apps/dashboard/pkg/migration/testdata/input/v2.panels-and-services.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/latest_version/v2.panels-and-services.v42.json create mode 100644 apps/dashboard/pkg/migration/testdata/output/single_version/v2.panels-and-services.v2.json diff --git a/apps/dashboard/pkg/migration/frontend_defaults.go b/apps/dashboard/pkg/migration/frontend_defaults.go index 08a9afe8475..0451e45b8de 100644 --- a/apps/dashboard/pkg/migration/frontend_defaults.go +++ b/apps/dashboard/pkg/migration/frontend_defaults.go @@ -946,32 +946,13 @@ func applyPanelAutoMigration(panel map[string]interface{}) { var newType string // Graph needs special logic as it can be migrated to multiple panels - if panelType == "graph" { + // Including graphite which was previously migrated to graph in the schema version 2 migration in DashboardMigrator.ts + // but this was a bug because in there graphite was set to graph, but since those migrations run + // after PanelModel.restoreModel where autoMigrateFrom is set, this caused the graph migration to be skipped. + // And this resulted in a dashboard with invalid panels. + if panelType == "graph" || panelType == "graphite" { // Check xaxis mode for special cases - if xaxis, ok := panel["xaxis"].(map[string]interface{}); ok { - if mode, ok := xaxis["mode"].(string); ok { - switch mode { - case "series": - // Check legend values for bargauge - if legend, ok := panel["legend"].(map[string]interface{}); ok { - if values, ok := legend["values"].(bool); ok && values { - newType = "bargauge" - } else { - newType = "barchart" - } - } else { - newType = "barchart" - } - case "histogram": - newType = "histogram" - } - } - } - - // Default graph migration to timeseries - if newType == "" { - newType = "timeseries" - } + newType = getGraphAutoMigration(panel) } else { // Check autoMigrateAngular mapping autoMigrateAngular := map[string]string{ @@ -995,6 +976,36 @@ func applyPanelAutoMigration(panel map[string]interface{}) { } } +func getGraphAutoMigration(panel map[string]interface{}) string { + newType := "" + if xaxis, ok := panel["xaxis"].(map[string]interface{}); ok { + if mode, ok := xaxis["mode"].(string); ok { + switch mode { + case "series": + // Check legend values for bargauge + if legend, ok := panel["legend"].(map[string]interface{}); ok { + if values, ok := legend["values"].(bool); ok && values { + newType = "bargauge" + } else { + newType = "barchart" + } + } else { + newType = "barchart" + } + case "histogram": + newType = "histogram" + } + } + } + + // Default graph migration to timeseries + if newType == "" { + newType = "timeseries" + } + + return newType +} + // removeNullValuesRecursively removes null values from nested objects and arrays // This matches the frontend's JSON.stringify/parse behavior func removeNullValuesRecursively(data interface{}) { diff --git a/apps/dashboard/pkg/migration/schemaversion/migrations.go b/apps/dashboard/pkg/migration/schemaversion/migrations.go index a577b7f2bba..a278c2e685e 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migrations.go +++ b/apps/dashboard/pkg/migration/schemaversion/migrations.go @@ -7,7 +7,7 @@ import ( ) const ( - MIN_VERSION = 2 + MIN_VERSION = 0 LATEST_VERSION = 42 ) @@ -35,6 +35,7 @@ type PanelPluginInfo struct { func GetMigrations(dsInfoProvider DataSourceInfoProvider) map[int]SchemaVersionMigrationFunc { return map[int]SchemaVersionMigrationFunc{ + 2: V2, 3: V3, 4: V4, 5: V5, diff --git a/apps/dashboard/pkg/migration/schemaversion/v2.go b/apps/dashboard/pkg/migration/schemaversion/v2.go new file mode 100644 index 00000000000..4e9636ad808 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v2.go @@ -0,0 +1,71 @@ +package schemaversion + +import "context" + +// V2 migrates dashboard from schema version 0 or 1 to 2. +// This migration handles the legacy services.filter structure. +// It matches the frontend DashboardMigrator.ts logic for oldVersion < 2 && finalTargetVersion >= 2. +// +// Key migrations: +// 1. Services filter migration: old.services.filter.time -> dashboard.time +// 2. Services filter migration: old.services.filter.list -> dashboard.templating.list +// +// Example before migration: +// +// { +// "schemaVersion": 1, +// "services": { +// "filter": { +// "time": {"from": "now-1h", "to": "now"}, +// "list": [{"name": "var1", "type": "query"}] +// } +// } +// } +// +// Example after migration: +// +// { +// "schemaVersion": 2, +// "time": {"from": "now-1h", "to": "now"}, +// "templating": { +// "list": [{"name": "var1", "type": "query"}] +// } +// } +func V2(_ context.Context, dashboard map[string]interface{}) error { + dashboard["schemaVersion"] = 2 + + // Migrate services.filter structure + migrateServicesFilter(dashboard) + + return nil +} + +// migrateServicesFilter migrates the legacy services.filter structure +func migrateServicesFilter(dashboard map[string]interface{}) { + services, ok := dashboard["services"].(map[string]interface{}) + if !ok { + return + } + + filter, ok := services["filter"].(map[string]interface{}) + if !ok { + return + } + + // Migrate time property + if time, ok := filter["time"]; ok { + dashboard["time"] = time + } + + // Migrate templating list + if list, ok := filter["list"]; ok { + if _, exists := dashboard["templating"]; !exists { + dashboard["templating"] = map[string]interface{}{} + } + templating := dashboard["templating"].(map[string]interface{}) + templating["list"] = list + } + + // Remove the services property after migration + delete(dashboard, "services") +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v2_test.go b/apps/dashboard/pkg/migration/schemaversion/v2_test.go new file mode 100644 index 00000000000..69ebb5e0b22 --- /dev/null +++ b/apps/dashboard/pkg/migration/schemaversion/v2_test.go @@ -0,0 +1,216 @@ +package schemaversion_test + +import ( + "testing" + + "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" +) + +func TestV2(t *testing.T) { + tests := []migrationTestCase{ + { + name: "services filter migration moves time and templating list", + input: map[string]interface{}{ + "title": "V2 Services Filter Migration Test", + "schemaVersion": 1, + "services": map[string]interface{}{ + "filter": map[string]interface{}{ + "time": map[string]interface{}{ + "from": "now-1h", + "to": "now", + }, + "list": []interface{}{ + map[string]interface{}{ + "name": "var1", + "type": "query", + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V2 Services Filter Migration Test", + "schemaVersion": 2, + "time": map[string]interface{}{ + "from": "now-1h", + "to": "now", + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "var1", + "type": "query", + }, + }, + }, + }, + }, + { + name: "comprehensive services filter migration", + input: map[string]interface{}{ + "title": "V2 Comprehensive Services Migration Test", + "schemaVersion": 1, + "services": map[string]interface{}{ + "filter": map[string]interface{}{ + "time": map[string]interface{}{ + "from": "now-6h", + "to": "now", + }, + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "query", + }, + }, + }, + }, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "graphite", + "title": "CPU Usage", + }, + }, + }, + expected: map[string]interface{}{ + "title": "V2 Comprehensive Services Migration Test", + "schemaVersion": 2, + "time": map[string]interface{}{ + "from": "now-6h", + "to": "now", + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "server", + "type": "query", + }, + }, + }, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "graphite", // Panel types are not migrated by V2 anymore + "title": "CPU Usage", + }, + }, + }, + }, + { + name: "dashboard with no services or panels", + input: map[string]interface{}{ + "title": "V2 Minimal Migration Test", + "schemaVersion": 1, + }, + expected: map[string]interface{}{ + "title": "V2 Minimal Migration Test", + "schemaVersion": 2, + }, + }, + { + name: "services filter with only time (no list)", + input: map[string]interface{}{ + "title": "V2 Services Time Only Test", + "schemaVersion": 1, + "services": map[string]interface{}{ + "filter": map[string]interface{}{ + "time": map[string]interface{}{ + "from": "now-2h", + "to": "now", + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V2 Services Time Only Test", + "schemaVersion": 2, + "time": map[string]interface{}{ + "from": "now-2h", + "to": "now", + }, + }, + }, + { + name: "services filter with only list (no time)", + input: map[string]interface{}{ + "title": "V2 Services List Only Test", + "schemaVersion": 1, + "services": map[string]interface{}{ + "filter": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "env", + "type": "custom", + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V2 Services List Only Test", + "schemaVersion": 2, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "env", + "type": "custom", + }, + }, + }, + }, + }, + { + name: "panels remain unchanged when no services filter", + input: map[string]interface{}{ + "title": "V2 Panels Unchanged Test", + "schemaVersion": 1, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "graphite", + "legend": true, + "y_format": "short", + "y2_format": "bytes", + "grid": map[string]interface{}{ + "min": 0, + "max": 100, + }, + }, + map[string]interface{}{ + "id": 2, + "type": "graph", + "legend": map[string]interface{}{ + "show": false, + }, + }, + }, + }, + expected: map[string]interface{}{ + "title": "V2 Panels Unchanged Test", + "schemaVersion": 2, + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "type": "graphite", // Panel migrations handled by auto-migration + "legend": true, + "y_format": "short", + "y2_format": "bytes", + "grid": map[string]interface{}{ + "min": 0, + "max": 100, + }, + }, + map[string]interface{}{ + "id": 2, + "type": "graph", + "legend": map[string]interface{}{ + "show": false, + }, + }, + }, + }, + }, + } + + runMigrationTests(t, tests, schemaversion.V2) +} diff --git a/apps/dashboard/pkg/migration/testdata/input/v2.panels-and-services.json b/apps/dashboard/pkg/migration/testdata/input/v2.panels-and-services.json new file mode 100644 index 00000000000..9053a37e5aa --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/input/v2.panels-and-services.json @@ -0,0 +1,89 @@ +{ + "schemaVersion": 1, + "title": "V2 Comprehensive Migration Test Dashboard", + "services": { + "filter": { + "time": { + "from": "now-6h", + "to": "now" + }, + "list": [ + { + "name": "server", + "type": "query", + "datasource": "prometheus", + "query": "label_values(server)" + }, + { + "name": "env", + "type": "custom", + "options": [ + {"text": "Production", "value": "prod"}, + {"text": "Staging", "value": "stage"} + ] + } + ] + } + }, + "panels": [ + { + "id": 1, + "type": "graphite", + "title": "CPU Usage", + "legend": true, + "grid": { + "min": 0, + "max": 100 + }, + "y_format": "percent", + "y2_format": "short", + "targets": [ + { + "refId": "A", + "target": "cpu.usage" + } + ] + }, + { + "id": 2, + "type": "graph", + "title": "Memory Usage", + "legend": false, + "grid": { + "min": 0 + }, + "y_format": "bytes", + "targets": [ + { + "refId": "A", + "target": "memory.usage" + } + ] + }, + { + "id": 3, + "type": "table", + "title": "Server Stats", + "legend": true, + "grid": { + "min": 0, + "max": 100 + }, + "y_format": "short", + "y2_format": "bytes" + }, + { + "id": 4, + "type": "graphite", + "title": "Disk I/O", + "legend": true, + "y2_format": "Bps", + "targets": [ + { + "refId": "A", + "target": "disk.io" + } + ] + } + ] +} diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v2.panels-and-services.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v2.panels-and-services.v42.json new file mode 100644 index 00000000000..9bf896f9bab --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v2.panels-and-services.v42.json @@ -0,0 +1,168 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graphite", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "max": 100, + "min": 0 + }, + "id": 1, + "legend": true, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "target": "cpu.usage" + } + ], + "title": "CPU Usage", + "type": "timeseries", + "y2_format": "short", + "y_format": "percent" + }, + { + "autoMigrateFrom": "graph", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "min": 0 + }, + "id": 2, + "legend": false, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "target": "memory.usage" + } + ], + "title": "Memory Usage", + "type": "timeseries", + "y_format": "bytes" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "grid": { + "max": 100, + "min": 0 + }, + "id": 3, + "legend": true, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A" + } + ], + "title": "Server Stats", + "type": "table", + "y2_format": "bytes", + "y_format": "short" + }, + { + "autoMigrateFrom": "graphite", + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 4, + "legend": true, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "refId": "A", + "target": "disk.io" + } + ], + "title": "Disk I/O", + "type": "timeseries", + "y2_format": "Bps" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "datasource": "prometheus", + "name": "server", + "options": [], + "query": "label_values(server)", + "refresh": 1, + "type": "query" + }, + { + "name": "env", + "options": [ + { + "text": "Production", + "value": "prod" + }, + { + "text": "Staging", + "value": "stage" + } + ], + "type": "custom" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V2 Comprehensive Migration Test Dashboard", + "weekStart": "" +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v2.panels-and-services.v2.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v2.panels-and-services.v2.json new file mode 100644 index 00000000000..c4059848635 --- /dev/null +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v2.panels-and-services.v2.json @@ -0,0 +1,121 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "autoMigrateFrom": "graphite", + "grid": { + "max": 100, + "min": 0 + }, + "id": 1, + "legend": true, + "targets": [ + { + "refId": "A", + "target": "cpu.usage" + } + ], + "title": "CPU Usage", + "type": "timeseries", + "y2_format": "short", + "y_format": "percent" + }, + { + "autoMigrateFrom": "graph", + "grid": { + "min": 0 + }, + "id": 2, + "legend": false, + "targets": [ + { + "refId": "A", + "target": "memory.usage" + } + ], + "title": "Memory Usage", + "type": "timeseries", + "y_format": "bytes" + }, + { + "grid": { + "max": 100, + "min": 0 + }, + "id": 3, + "legend": true, + "title": "Server Stats", + "type": "table", + "y2_format": "bytes", + "y_format": "short" + }, + { + "autoMigrateFrom": "graphite", + "id": 4, + "legend": true, + "targets": [ + { + "refId": "A", + "target": "disk.io" + } + ], + "title": "Disk I/O", + "type": "timeseries", + "y2_format": "Bps" + } + ], + "schemaVersion": 2, + "tags": [], + "templating": { + "list": [ + { + "datasource": "prometheus", + "name": "server", + "options": [], + "query": "label_values(server)", + "type": "query" + }, + { + "name": "env", + "options": [ + { + "text": "Production", + "value": "prod" + }, + { + "text": "Staging", + "value": "stage" + } + ], + "type": "custom" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "V2 Comprehensive Migration Test Dashboard", + "weekStart": "" +} \ No newline at end of file diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 0fecfa471ed..8f0d21a4b5c 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2970,7 +2970,7 @@ "count": 2 }, "@typescript-eslint/no-explicit-any": { - "count": 17 + "count": 16 } }, "public/app/features/dashboard/state/DashboardModel.repeat.test.ts": { diff --git a/pkg/registry/apis/provisioning/jobs/export/resources.go b/pkg/registry/apis/provisioning/jobs/export/resources.go index 445f25faca4..35793972d89 100644 --- a/pkg/registry/apis/provisioning/jobs/export/resources.go +++ b/pkg/registry/apis/provisioning/jobs/export/resources.go @@ -41,9 +41,11 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions, if kind.GroupResource() == resources.DashboardResource.GroupResource() { var v2clientAlphaV1, v2clientAlphaV2 dynamic.ResourceInterface shim = func(ctx context.Context, item *unstructured.Unstructured) (*unstructured.Unstructured, error) { - failed, _, _ := unstructured.NestedBool(item.Object, "status", "conversion", "failed") - if failed { - storedVersion, _, _ := unstructured.NestedString(item.Object, "status", "conversion", "storedVersion") + // Check if there's a stored version in the conversion status. + // This indicates the original API version the dashboard was created with, + // which should be preserved during export regardless of whether conversion succeeded or failed. + storedVersion, _, _ := unstructured.NestedString(item.Object, "status", "conversion", "storedVersion") + if storedVersion != "" { // For v2 we need to request the original version if strings.HasPrefix(storedVersion, "v2alpha1") { if v2clientAlphaV1 == nil { @@ -73,6 +75,13 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions, return nil, fmt.Errorf("unsupported dashboard version: %s", storedVersion) } + + // If conversion failed but there's no storedVersion, this is an error condition + failed, _, _ := unstructured.NestedBool(item.Object, "status", "conversion", "failed") + if failed { + return nil, fmt.Errorf("conversion failed but no storedVersion available") + } + return item, nil } } diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index 43b88a92eb0..b6e9634da87 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -1,4 +1,4 @@ -import { each, find, findIndex, flattenDeep, isArray, isBoolean, isString, map, max, some } from 'lodash'; +import { each, find, findIndex, flattenDeep, isArray, isString, map, max, some } from 'lodash'; import { AnnotationQuery, @@ -119,50 +119,8 @@ export class DashboardMigrator { } } - panelUpgrades.push((panel: any) => { - // rename panel type - if (panel.type === 'graphite') { - panel.type = 'graph'; - } - - if (panel.type !== 'graph') { - return panel; - } - - if (isBoolean(panel.legend)) { - panel.legend = { show: panel.legend }; - } - - if (panel.grid) { - if (panel.grid.min) { - panel.grid.leftMin = panel.grid.min; - delete panel.grid.min; - } - - if (panel.grid.max) { - panel.grid.leftMax = panel.grid.max; - delete panel.grid.max; - } - } - - if (panel.y_format) { - if (!panel.y_formats) { - panel.y_formats = []; - } - panel.y_formats[0] = panel.y_format; - delete panel.y_format; - } - - if (panel.y2_format) { - if (!panel.y_formats) { - panel.y_formats = []; - } - panel.y_formats[1] = panel.y2_format; - delete panel.y2_format; - } - - return panel; - }); + // we used to have graphite panel type migration logic here + // but this is handled by auto migration, see public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts } // schema version 3 changes diff --git a/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts b/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts index 922a3c70ccd..1872dea7f60 100644 --- a/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts +++ b/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts @@ -103,10 +103,14 @@ describe('Backend / Frontend single version migration result comparison', () => expect(backendMigrationResult.schemaVersion).toEqual(targetVersion); // Migrate dashboard in Frontend. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let migratedTemplatingList: any[] = jsonInput?.templating?.list ?? []; const frontendModel = new DashboardModel(jsonInput, undefined, { targetSchemaVersion: targetVersion, - getVariablesFromState: () => jsonInput?.templating?.list ?? [], + getVariablesFromState: () => migratedTemplatingList, }); + // Update the templating list reference after migration + migratedTemplatingList = frontendModel.templating?.list ?? []; const frontendMigrationResult = frontendModel.getSaveModelClone(); diff --git a/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts b/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts index bef909ca13c..f97715f088e 100644 --- a/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts +++ b/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts @@ -71,9 +71,13 @@ describe('Backend / Frontend result comparison', () => { expect(backendMigrationResult.schemaVersion).toEqual(DASHBOARD_SCHEMA_VERSION); // Migrate dashboard in Frontend. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let migratedTemplatingList: any[] = jsonInput?.templating?.list ?? []; const frontendModel = new DashboardModel(jsonInput, undefined, { - getVariablesFromState: () => jsonInput?.templating?.list ?? [], + getVariablesFromState: () => migratedTemplatingList, }); + // Update the templating list reference after migration + migratedTemplatingList = frontendModel.templating?.list ?? []; const frontendMigrationResult = frontendModel.getSaveModelClone(); diff --git a/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts b/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts index 114466c87ec..9e021585502 100644 --- a/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts +++ b/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts @@ -2,7 +2,11 @@ import { autoMigrateAngular } from './PanelModel'; export function getPanelPluginToMigrateTo(panel: any): string | undefined { // Graph needs special logic as it can be migrated to multiple panels - if (panel.type === 'graph') { + // Also, graphite was previously migrated to graph in the schema version 2 migration in DashboardMigrator.ts + // but this was a bug because in there graphite was set to graph, but since those migrations run + // after PanelModel.restoreModel where autoMigrateFrom is set, this caused the graph migration to be skipped. + // And this resulted in a dashboard with invalid panels. + if (panel.type === 'graph' || panel.type === 'graphite') { if (panel.xaxis?.mode === 'series') { if (panel.legend?.values) { return 'bargauge'; From e2a55738afbdf72b98dd0cc35e8f949350494e54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 17:00:03 +0000 Subject: [PATCH 42/75] Bump grafana/shared-workflows/actions/send-slack-message@send-slack-message/v2.0.3 from 2.0.3 to 2.0.4 (#112317) Bump grafana/shared-workflows/actions/send-slack-message@send-slack-message/v2.0.3 Bumps [grafana/shared-workflows/actions/send-slack-message@send-slack-message/v2.0.3](https://github.com/grafana/shared-workflows) from 2.0.3 to 2.0.4. - [Release notes](https://github.com/grafana/shared-workflows/releases) - [Changelog](https://github.com/grafana/shared-workflows/blob/main/release-please-config.json) - [Commits](https://github.com/grafana/shared-workflows/compare/send-slack-message/v2.0.3...send-slack-message/v2.0.4) --- updated-dependencies: - dependency-name: grafana/shared-workflows/actions/send-slack-message@send-slack-message/v2.0.3 dependency-version: 2.0.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/alerting-update-module.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/alerting-update-module.yml b/.github/workflows/alerting-update-module.yml index dc623c8b333..c126f67131a 100644 --- a/.github/workflows/alerting-update-module.yml +++ b/.github/workflows/alerting-update-module.yml @@ -148,7 +148,7 @@ jobs: echo "🔗 [View Pull Request]($PR_URL)" } >> "$GITHUB_STEP_SUMMARY" - name: Send Slack Message - uses: grafana/shared-workflows/actions/send-slack-message@send-slack-message/v2.0.3 + uses: grafana/shared-workflows/actions/send-slack-message@send-slack-message/v2.0.4 with: method: 'chat.postMessage' # send to alerting-reviews channel From f37f16cf0d0cd2b51b3847d52db5c49b224c9c51 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 17:22:33 +0000 Subject: [PATCH 43/75] Bump golangci/golangci-lint-action from 7.0.0 to 8.0.0 (#109018) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 7.0.0 to 8.0.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/1481404843c368bc19ca9406f87d6e0fc97bdcfd...4afd733a84b1f43292c63897423277bb7f4313a9) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/go-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml index cdce15360a4..87cfbcb81d4 100644 --- a/.github/workflows/go-lint.yml +++ b/.github/workflows/go-lint.yml @@ -66,7 +66,7 @@ jobs: with: go-version-file: ./go.mod - name: golangci-lint - uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd + uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 with: version: v2.5.0 args: | From 628664d5878d8bdf86bc87ef73b9db52b9eec5a1 Mon Sep 17 00:00:00 2001 From: Dave Henderson <dave.henderson@grafana.com> Date: Wed, 15 Oct 2025 13:26:02 -0400 Subject: [PATCH 44/75] ci(dependabot): Update config and add groups (#112454) Signed-off-by: Dave Henderson <dave.henderson@grafana.com> --- .github/dependabot.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1c62ef810eb..0f7096c314b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,14 @@ version: 2 updates: - package-ecosystem: "github-actions" + commit-message: + prefix: deps(actions) directory: "/" schedule: interval: "daily" - package-ecosystem: "gomod" + commit-message: + prefix: deps(go) directories: - "/" - "/apps/playlist" @@ -25,8 +29,22 @@ updates: interval: "daily" time: "02:00" timezone: Etc/UTC - open-pull-requests-limit: 10 + open-pull-requests-limit: 20 + # group some updates together for easier review + groups: + go.opentelemetry.io: + patterns: + - "go.opentelemetry.io/*" + k8s.io: + patterns: + - "k8s.io/*" + aws-sdk-go: + patterns: + - "github.com/aws/aws-sdk-go*" + - "github.com/aws/smithy-go" - package-ecosystem: "docker" + commit-message: + prefix: deps(docker) directories: - "/" - "/packaging/docker/custom" @@ -35,4 +53,4 @@ updates: interval: "daily" time: "02:00" timezone: Etc/UTC - open-pull-requests-limit: 10 + open-pull-requests-limit: 20 From 7bc97d5fa8af96a32aaf9787fd5a492253b069cf Mon Sep 17 00:00:00 2001 From: Seunghun Shin <42256738+softho0n@users.noreply.github.com> Date: Thu, 16 Oct 2025 02:28:10 +0900 Subject: [PATCH 45/75] Docs: Add contact point specific text formatting examples (#112276) * Docs: Add contact point specific text formatting examples to notification templates * Add Slack formatting examples with *bold* and _italic_ syntax * Clarify that text formatting depends on contact point type * Docs: Add contact point specific text formatting examples to notification templates * Reflect review that fix docs --- .../template-notifications/examples.md | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/sources/alerting/configure-notifications/template-notifications/examples.md b/docs/sources/alerting/configure-notifications/template-notifications/examples.md index 7927d367c94..f44ada3c4bf 100644 --- a/docs/sources/alerting/configure-notifications/template-notifications/examples.md +++ b/docs/sources/alerting/configure-notifications/template-notifications/examples.md @@ -89,7 +89,53 @@ This page provides various examples illustrating how to template common notifica Notification templates can access the [notification data](ref:reference-notification-data) using the dot (`.`). The following examples demonstrate some basic uses of the [template language](ref:language). -For instance, to check if there are common labels (`.CommonLabels`) for all alerts in the notification, use `if`: +### Text formatting examples + +Text formatting in notification templates varies by contact point type. Each platform has its own formatting syntax: + +#### Slack formatting + +For Slack contact points, you can use: + +```go +{{ define "slack_formatted_message" -}} +*This text will be bold in Slack* +_This text will be italic in Slack_ +Regular text without formatting +{{ end }} +``` + +Execute the template: + +```go +{{ template "slack_formatted_message" . }} +``` + +```template_output +*This text will be bold in Slack* +_This text will be italic in Slack_ +Regular text without formatting +``` + +#### Additional contact point types + +{{< admonition type="note" >}} +Each contact point type has its own formatting requirements: + +- Some support Markdown-style formatting +- Others use HTML tags +- Some may not support formatting at all + +Check your specific contact point's documentation for supported formatting syntax. +{{< /admonition >}} + +## Template logic examples + +Beyond text formatting, you can use template logic to create dynamic notification content. The following examples demonstrate basic template operations: + +### Conditional logic + +To check if there are common labels (`.CommonLabels`) for all alerts in the notification, use `if`: ```go {{ define "custom_message" -}} @@ -101,6 +147,8 @@ There are no common labels {{ end }} ``` +### Iterating over alerts + To iterate on the alerts in the notification and print a specific label, use `range` and `index`: ```go From d8c6af2fa7c2d9da522acc8835e80e0448705ee3 Mon Sep 17 00:00:00 2001 From: Jack Baldry <jack.baldry@grafana.com> Date: Wed, 15 Oct 2025 18:41:15 +0100 Subject: [PATCH 46/75] Consolidate Grafana visualization documentation (#112393) Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> --- .github/CODEOWNERS | 16 +++--- docs/sources/visualizations/_index.md | 48 +++++++++++++++++ .../{ => visualizations}/dashboards/_index.md | 5 +- .../assess-dashboard-usage/index.md | 9 ++-- .../dashboards/build-dashboards/_index.md | 2 + .../annotate-visualizations/index.md | 6 ++- .../build-dashboards/best-practices/index.md | 13 ++--- .../create-dashboard-url-variables/index.md | 5 +- .../create-dashboard/index.md | 3 +- .../create-dynamic-dashboard/index.md | 2 + .../import-dashboards/index.md | 5 +- .../manage-dashboard-links/index.md | 13 ++--- .../manage-library-panels/index.md | 17 ++++--- .../manage-version-history/index.md | 5 +- .../modify-dashboard-settings/index.md | 2 + .../view-dashboard-json-model/index.md | 5 +- .../create-manage-playlists/index.md | 5 +- .../dashboards/create-reports/_index.md | 11 ++-- .../create-reports/report-settings/index.md | 2 + .../dashboards/manage-dashboards/index.md | 9 ++-- .../dashboards/search-dashboards/index.md | 2 + .../share-dashboards-panels/_index.md | 17 ++++--- .../shared-dashboards/index.md | 3 +- .../troubleshoot-dashboards/index.md | 5 +- .../dashboards/use-dashboards/index.md | 15 +++--- .../dashboards/variables/_index.md | 7 +-- .../variables/add-template-variables/index.md | 51 ++++++++++--------- .../variables/inspect-variable/index.md | 5 +- .../variables/variable-syntax/index.md | 7 +-- .../{ => visualizations}/explore/_index.md | 4 +- .../explore/correlations-editor-in-explore.md | 2 + .../explore/explore-inspector.md | 3 ++ .../explore/get-started-with-explore.md | 2 +- .../explore/logs-integration.md | 2 + .../explore/query-management.md | 2 + .../explore/trace-integration.md | 2 + .../panels-visualizations/_index.md | 7 +-- .../configure-data-links/index.md | 13 ++--- .../configure-legend/index.md | 5 +- .../configure-overrides/index.md | 17 ++++--- .../configure-panel-options/index.md | 11 ++-- .../configure-standard-options/index.md | 17 ++++--- .../configure-thresholds/index.md | 15 +++--- .../configure-tooltips/index.md | 0 .../configure-value-mappings/index.md | 19 +++---- .../panel-editor-overview/index.md | 21 ++++---- .../panel-inspector/index.md | 9 ++-- .../panel-overview/index.md | 2 + .../query-transform-data/_index.md | 27 +++++----- .../calculation-types/index.md | 9 ++-- .../expression-queries/index.md | 9 ++-- .../query-transform-data/share-query/index.md | 3 +- .../sql-expressions/index.md | 0 .../transform-data/index.md | 28 +++++----- .../troubleshoot-queries/index.md | 3 +- .../visualizations/_index.md | 13 ++--- .../visualizations/alert-list/index.md | 9 ++-- .../visualizations/annotations/index.md | 7 +-- .../visualizations/bar-chart/index.md | 5 +- .../visualizations/bar-gauge/index.md | 7 +-- .../visualizations/candlestick/index.md | 7 +-- .../visualizations/canvas/index.md | 5 +- .../visualizations/dashboard-list/index.md | 9 ++-- .../visualizations/datagrid/index.md | 7 +-- .../visualizations/flame-graph/index.md | 3 +- .../visualizations/gauge/index.md | 7 +-- .../visualizations/geomap/index.md | 43 ++++++++-------- .../visualizations/heatmap/index.md | 5 +- .../visualizations/histogram/index.md | 7 +-- .../visualizations/logs/index.md | 9 ++-- .../visualizations/news/index.md | 5 +- .../visualizations/node-graph/index.md | 5 +- .../visualizations/pie-chart/index.md | 5 +- .../visualizations/stat/index.md | 11 ++-- .../visualizations/state-timeline/index.md | 5 +- .../visualizations/status-history/index.md | 5 +- .../visualizations/table/index.md | 13 ++--- .../visualizations/text/index.md | 9 ++-- .../visualizations/time-series/index.md | 35 ++++++------- .../visualizations/traces/index.md | 3 +- .../visualizations/trend/index.md | 2 + .../visualizations/xy-chart/index.md | 2 + .../simplified-exploration/_index.md | 9 ++-- 83 files changed, 462 insertions(+), 317 deletions(-) create mode 100644 docs/sources/visualizations/_index.md rename docs/sources/{ => visualizations}/dashboards/_index.md (94%) rename docs/sources/{ => visualizations}/dashboards/assess-dashboard-usage/index.md (92%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/_index.md (86%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/annotate-visualizations/index.md (96%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/best-practices/index.md (95%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/create-dashboard-url-variables/index.md (93%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/create-dashboard/index.md (97%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/create-dynamic-dashboard/index.md (99%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/import-dashboards/index.md (89%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/manage-dashboard-links/index.md (94%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/manage-library-panels/index.md (85%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/manage-version-history/index.md (88%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/modify-dashboard-settings/index.md (98%) rename docs/sources/{ => visualizations}/dashboards/build-dashboards/view-dashboard-json-model/index.md (97%) rename docs/sources/{ => visualizations}/dashboards/create-manage-playlists/index.md (96%) rename docs/sources/{ => visualizations}/dashboards/create-reports/_index.md (97%) rename docs/sources/{ => visualizations}/dashboards/create-reports/report-settings/index.md (96%) rename docs/sources/{ => visualizations}/dashboards/manage-dashboards/index.md (92%) rename docs/sources/{ => visualizations}/dashboards/search-dashboards/index.md (98%) rename docs/sources/{ => visualizations}/dashboards/share-dashboards-panels/_index.md (96%) rename docs/sources/{ => visualizations}/dashboards/share-dashboards-panels/shared-dashboards/index.md (98%) rename docs/sources/{ => visualizations}/dashboards/troubleshoot-dashboards/index.md (91%) rename docs/sources/{ => visualizations}/dashboards/use-dashboards/index.md (95%) rename docs/sources/{ => visualizations}/dashboards/variables/_index.md (93%) rename docs/sources/{ => visualizations}/dashboards/variables/add-template-variables/index.md (94%) rename docs/sources/{ => visualizations}/dashboards/variables/inspect-variable/index.md (90%) rename docs/sources/{ => visualizations}/dashboards/variables/variable-syntax/index.md (94%) rename docs/sources/{ => visualizations}/explore/_index.md (91%) rename docs/sources/{ => visualizations}/explore/correlations-editor-in-explore.md (98%) rename docs/sources/{ => visualizations}/explore/explore-inspector.md (96%) rename docs/sources/{ => visualizations}/explore/get-started-with-explore.md (99%) rename docs/sources/{ => visualizations}/explore/logs-integration.md (99%) rename docs/sources/{ => visualizations}/explore/query-management.md (97%) rename docs/sources/{ => visualizations}/explore/trace-integration.md (99%) rename docs/sources/{ => visualizations}/panels-visualizations/_index.md (89%) rename docs/sources/{ => visualizations}/panels-visualizations/configure-data-links/index.md (96%) rename docs/sources/{ => visualizations}/panels-visualizations/configure-legend/index.md (95%) rename docs/sources/{ => visualizations}/panels-visualizations/configure-overrides/index.md (92%) rename docs/sources/{ => visualizations}/panels-visualizations/configure-panel-options/index.md (91%) rename docs/sources/{ => visualizations}/panels-visualizations/configure-standard-options/index.md (95%) rename docs/sources/{ => visualizations}/panels-visualizations/configure-thresholds/index.md (92%) rename docs/sources/{ => visualizations}/panels-visualizations/configure-tooltips/index.md (100%) rename docs/sources/{ => visualizations}/panels-visualizations/configure-value-mappings/index.md (90%) rename docs/sources/{ => visualizations}/panels-visualizations/panel-editor-overview/index.md (77%) rename docs/sources/{ => visualizations}/panels-visualizations/panel-inspector/index.md (83%) rename docs/sources/{ => visualizations}/panels-visualizations/panel-overview/index.md (98%) rename docs/sources/{ => visualizations}/panels-visualizations/query-transform-data/_index.md (93%) rename docs/sources/{ => visualizations}/panels-visualizations/query-transform-data/calculation-types/index.md (81%) rename docs/sources/{ => visualizations}/panels-visualizations/query-transform-data/expression-queries/index.md (94%) rename docs/sources/{ => visualizations}/panels-visualizations/query-transform-data/share-query/index.md (89%) rename docs/sources/{ => visualizations}/panels-visualizations/query-transform-data/sql-expressions/index.md (100%) rename docs/sources/{ => visualizations}/panels-visualizations/query-transform-data/transform-data/index.md (97%) rename docs/sources/{ => visualizations}/panels-visualizations/query-transform-data/troubleshoot-queries/index.md (85%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/_index.md (95%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/alert-list/index.md (91%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/annotations/index.md (90%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/bar-chart/index.md (97%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/bar-gauge/index.md (95%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/candlestick/index.md (98%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/canvas/index.md (99%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/dashboard-list/index.md (90%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/datagrid/index.md (94%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/flame-graph/index.md (98%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/gauge/index.md (96%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/geomap/index.md (94%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/heatmap/index.md (97%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/histogram/index.md (94%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/logs/index.md (92%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/news/index.md (90%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/node-graph/index.md (98%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/pie-chart/index.md (96%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/stat/index.md (95%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/state-timeline/index.md (96%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/status-history/index.md (95%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/table/index.md (97%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/text/index.md (87%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/time-series/index.md (79%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/traces/index.md (98%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/trend/index.md (96%) rename docs/sources/{ => visualizations}/panels-visualizations/visualizations/xy-chart/index.md (98%) rename docs/sources/{explore => visualizations}/simplified-exploration/_index.md (92%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b9daf7a2487..f175117e4db 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -41,18 +41,20 @@ /docs/sources/ @irenerl24 /docs/sources/alerting/ @JohnnyK-Grafana -/docs/sources/dashboards/ @imatwawana /docs/sources/datasources/ @lwandz13 -/docs/sources/panels-visualizations/ @imatwawana /docs/sources/upgrade-guide/ @jtvdez /docs/sources/whatsnew/ @jtvdez -/docs/sources/developers/plugins/ @grafana/plugins-platform-frontend @grafana/plugins-platform-backend -/docs/sources/dashboards/share-dashboards-panels/_index.md @imatwawana @jtvdez -/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md @jtvdez -/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md @imatwawana @baldm0mma -/docs/sources/panels-visualizations/query-transform-data/sql-expressions/index.md @lwandz13 @irenerl24 +/docs/sources/developers/plugins/ @grafana/plugins-platform-frontend @grafana/plugins-platform-backend +/docs/sources/visualizations/dashboards/ @imatwawana +/docs/sources/visualizations/panels-visualizations/ @imatwawana + + +/docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md @imatwawana @jtvdez +/docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md @jtvdez +/docs/sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md @imatwawana @baldm0mma +/docs/sources/visualizations/panels-visualizations/query-transform-data/sql-expressions/index.md @lwandz13 @irenerl24 # END Technical documentation # Backend code diff --git a/docs/sources/visualizations/_index.md b/docs/sources/visualizations/_index.md new file mode 100644 index 00000000000..55c03c0f06d --- /dev/null +++ b/docs/sources/visualizations/_index.md @@ -0,0 +1,48 @@ +--- +aliases: + - /docs/grafana-cloud/visualization/ + - /docs/grafana-cloud/visualizations/ +description: Learn how to build dashboards and visualizations. +menuTitle: Visualize data +title: Visualize your data in Grafana +hero: + title: Visualize data + level: 1 + width: 100 + height: 100 + description: Easily collect, correlate, and visualize data with beautiful dashboards using Grafana—the solution that drives informed decisions, enhances system performance, and streamlines troubleshooting. +cards: + items: + - description: Allow you to query, transform, visualize, and understand your data no matter where it’s stored. + height: 24 + href: /docs/grafana-cloud/visualizations/dashboards/ + title: Dashboards + - description: Allow you to easily collect, correlate, and visualize data so you can make informed decisions in real time. + height: 24 + href: /docs/grafana-cloud/visualizations/panels-visualizations/ + title: Panels and visualizations + - description: Use the Drilldown apps to explore your telemetry data with a queryless experience. + height: 24 + href: /docs/grafana-cloud/visualizations/simplified-exploration/ + title: Drilldown apps + +weight: 500 +--- + +{{< docs/hero-simple key="hero" >}} + +--- + +## Overview + +A Grafana dashboard is a set of one or more panels that provide an at-a-glance view of related information. These panels are the basic building block in Grafana dashboards, and they're created using components that query and transform raw data from a data source into charts, graphs, and other visualizations. + +A data source can be an SQL database, Grafana Loki, Grafana Mimir, or a JSON-based API. It can even be a basic CSV file. Queries allow you to reduce the entirety of your data to a specific dataset, providing a more manageable visualization. Data source plugins take a query you want answered, retrieve the data from the data source, and reconcile the differences between the data model of the data source and the data model of Grafana dashboards. + +The growing suite of Grafana visualizations, ranging from time series graphs to heatmaps to cutting-edge 3D charts, provide you several different ways to present your data within a panel, depending on what best suits the data and your needs. If the data format in a visualization doesn’t meet your requirements, you can apply a transformation that manipulates the data returned by a query. + +With 150+ data source plugins, you can unify all your data sources into a single dashboard to streamline data monitoring and troubleshooting. With Grafana, you can translate, transform, and visualize data in flexible and versatile dashboards. + +## Explore + +{{< card-grid key="cards" type="simple" >}} diff --git a/docs/sources/dashboards/_index.md b/docs/sources/visualizations/dashboards/_index.md similarity index 94% rename from docs/sources/dashboards/_index.md rename to docs/sources/visualizations/dashboards/_index.md index c919c48debb..03c1b9724b7 100644 --- a/docs/sources/dashboards/_index.md +++ b/docs/sources/visualizations/dashboards/_index.md @@ -1,7 +1,8 @@ --- aliases: - - features/dashboard/dashboards/ # /docs/grafana/latest/features/dashboard/dashboards/ - - ./dashboards/previews/ # /docs/grafana/latest/dashboards/previews/ + - ../features/dashboard/dashboards/ # /docs/grafana/latest/features/dashboard/dashboards/ + - ../dashboards/previews/ # /docs/grafana/latest/dashboards/previews/ + - ../dashboards/ # /docs/grafana/next/dashboards/ labels: products: - cloud diff --git a/docs/sources/dashboards/assess-dashboard-usage/index.md b/docs/sources/visualizations/dashboards/assess-dashboard-usage/index.md similarity index 92% rename from docs/sources/dashboards/assess-dashboard-usage/index.md rename to docs/sources/visualizations/dashboards/assess-dashboard-usage/index.md index e989ec12470..2030cd1b2d8 100644 --- a/docs/sources/dashboards/assess-dashboard-usage/index.md +++ b/docs/sources/visualizations/dashboards/assess-dashboard-usage/index.md @@ -1,9 +1,10 @@ --- aliases: - - ../enterprise/usage-insights/ - - ../enterprise/usage-insights/dashboard-datasource-insights/ - - ../enterprise/usage-insights/improved-search/ - - ../enterprise/usage-insights/presence-indicator/ + - ../../enterprise/usage-insights/ # /docs/grafana/next/enterprise/usage-insights/ + - ../../enterprise/usage-insights/dashboard-datasource-insights/ # /docs/grafana/next/enterprise/usage-insights/dashboard-datasource-insights/ + - ../../enterprise/usage-insights/improved-search/ # /docs/grafana/next/enterprise/usage-insights/improved-search/ + - ../../enterprise/usage-insights/presence-indicator/ # /docs/grafana/next/enterprise/usage-insights/presence-indicator/ + - ../../dashboards/assess-dashboard-usage/ # /docs/grafana/next/dashboards/assess-dashboard-usage/ description: Understand how your Grafana instance is used keywords: - grafana diff --git a/docs/sources/dashboards/build-dashboards/_index.md b/docs/sources/visualizations/dashboards/build-dashboards/_index.md similarity index 86% rename from docs/sources/dashboards/build-dashboards/_index.md rename to docs/sources/visualizations/dashboards/build-dashboards/_index.md index 4978be9dd35..5432ff0671d 100644 --- a/docs/sources/dashboards/build-dashboards/_index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/_index.md @@ -21,6 +21,8 @@ refs: destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/variables/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/variables/ +aliases: + - ../../dashboards/build-dashboards/ # /docs/grafana/next/dashboards/build-dashboards/ --- # Build dashboards diff --git a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md b/docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md similarity index 96% rename from docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md rename to docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md index 81f6ce065f1..47f088783f5 100644 --- a/docs/sources/dashboards/build-dashboards/annotate-visualizations/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md @@ -1,7 +1,9 @@ --- aliases: - - ../../reference/annotations/ - - ../annotations/ + - ../../../dashboards/annotations/ # /docs/grafana/next/annotations/ + - ../../../dashboards/build-dashboards/annotate-visualizations/ # /docs/grafana/next/dashboards/build-dashboards/annotate-visualizations/ + - ../../../panels/visualizations/annotations/ # /docs/grafana/next/panels/visualizations/annotations/ + - ../../../reference/annotations/ # /docs/grafana/latest/reference/annotations/ keywords: - grafana - annotations diff --git a/docs/sources/dashboards/build-dashboards/best-practices/index.md b/docs/sources/visualizations/dashboards/build-dashboards/best-practices/index.md similarity index 95% rename from docs/sources/dashboards/build-dashboards/best-practices/index.md rename to docs/sources/visualizations/dashboards/build-dashboards/best-practices/index.md index e67a1a87ead..df7f3f2e648 100644 --- a/docs/sources/dashboards/build-dashboards/best-practices/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/best-practices/index.md @@ -1,11 +1,12 @@ --- aliases: - - ../../best-practices/ - - ../../best-practices/best-practices-for-creating-dashboards/ - - ../../best-practices/best-practices-for-managing-dashboards/ - - ../../best-practices/common-observability-strategies/ - - ../../best-practices/dashboard-management-maturity-levels/ - - ../../getting-started/strategies/ + - ../../../best-practices/ # /docs/grafana/next/best-practices/ + - ../../../best-practices/best-practices-for-creating-dashboards/ # /docs/grafana/next/best-practices/best-practices-for-creating-dashboards/ + - ../../../best-practices/best-practices-for-managing-dashboards/ # /docs/grafana/next/best-practices/best-practices-for-managing-dashboards/ + - ../../../best-practices/common-observability-strategies/ # /docs/grafana/next/best-practices/common-observability-strategies/ + - ../../../best-practices/dashboard-management-maturity-levels/ # /docs/grafana/next/best-practices/dashboard-management-maturity-levels/ + - ../../../getting-started/strategies/ # /docs/grafana/next/getting-started/strategies/ + - ../../../dashboards/build-dashboards/best-practices/ # /docs/grafana/next/dashboards/build-dashboards/best-practices/ description: Learn best practices for building and maintaining Grafana dashboards labels: products: diff --git a/docs/sources/dashboards/build-dashboards/create-dashboard-url-variables/index.md b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard-url-variables/index.md similarity index 93% rename from docs/sources/dashboards/build-dashboards/create-dashboard-url-variables/index.md rename to docs/sources/visualizations/dashboards/build-dashboards/create-dashboard-url-variables/index.md index 49639f74d01..b1f9d5ed609 100644 --- a/docs/sources/dashboards/build-dashboards/create-dashboard-url-variables/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard-url-variables/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../variables/url-variables/ - - ../../variables/variable-types/url-variables/ + - ../../../variables/url-variables/ # /docs/grafana/next/variables/url-variables/ + - ../../../variables/variable-types/url-variables/ # /docs/grafana/next/variables/variable-types/url-variables/ + - ../../../dashboards/build-dashboards/create-dashboard-url-variables/ # /docs/grafana/next/dashboards/build-dashboards/create-dashboard-url-variables/ keywords: - grafana - url variables diff --git a/docs/sources/dashboards/build-dashboards/create-dashboard/index.md b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md similarity index 97% rename from docs/sources/dashboards/build-dashboards/create-dashboard/index.md rename to docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md index 5368b509e59..33ac74c856d 100644 --- a/docs/sources/dashboards/build-dashboards/create-dashboard/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md @@ -1,6 +1,7 @@ --- aliases: - - add-organize-panels/ + - ../../../dashboards/build-dashboards/add-organize-panels/ # /docs/grafana/next/dashboards/build-dashboards/add-organize-panels/ + - ../../../dashboards/build-dashboards/create-dashboard/ # /docs/grafana/next/dashboards/build-dashboards/create-dashboard/ keywords: - panel - dashboard diff --git a/docs/sources/dashboards/build-dashboards/create-dynamic-dashboard/index.md b/docs/sources/visualizations/dashboards/build-dashboards/create-dynamic-dashboard/index.md similarity index 99% rename from docs/sources/dashboards/build-dashboards/create-dynamic-dashboard/index.md rename to docs/sources/visualizations/dashboards/build-dashboards/create-dynamic-dashboard/index.md index 0e9f99946bc..a77e4ca988f 100644 --- a/docs/sources/dashboards/build-dashboards/create-dynamic-dashboard/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/create-dynamic-dashboard/index.md @@ -72,6 +72,8 @@ refs: destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-overrides/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-overrides/ +aliases: + - ../../../dashboards/build-dashboards/create-dynamic-dashboard/ # /docs/grafana/next/dashboards/build-dashboards/create-dynamic-dashboard/ --- # Create and edit dynamic dashboards diff --git a/docs/sources/dashboards/build-dashboards/import-dashboards/index.md b/docs/sources/visualizations/dashboards/build-dashboards/import-dashboards/index.md similarity index 89% rename from docs/sources/dashboards/build-dashboards/import-dashboards/index.md rename to docs/sources/visualizations/dashboards/build-dashboards/import-dashboards/index.md index df660d1b351..a51746e7b87 100644 --- a/docs/sources/dashboards/build-dashboards/import-dashboards/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/import-dashboards/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../reference/export_import/ # /docs/grafana/<GRAFANA_VERSION>/reference/export_import/ - - ../export-import/ # /docs/grafana/<GRAFANA_VERSION>/dashboards/export-import/ + - ../../../reference/export_import/ # /docs/grafana/next/reference/export_import/ + - ../../../dashboards/export-import/ # /docs/grafana/next/dashboards/export-import/ + - ../../../dashboards/build-dashboards/import-dashboards/ # /docs/grafana/next/dashboards/build-dashboards/import-dashboards/ canonical: https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/import-dashboards/ keywords: - grafana diff --git a/docs/sources/dashboards/build-dashboards/manage-dashboard-links/index.md b/docs/sources/visualizations/dashboards/build-dashboards/manage-dashboard-links/index.md similarity index 94% rename from docs/sources/dashboards/build-dashboards/manage-dashboard-links/index.md rename to docs/sources/visualizations/dashboards/build-dashboards/manage-dashboard-links/index.md index 99f4cf3bf32..cddf9a5b9f5 100644 --- a/docs/sources/dashboards/build-dashboards/manage-dashboard-links/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/manage-dashboard-links/index.md @@ -1,11 +1,12 @@ --- aliases: - - ../../features/navigation-links/ - - ../../linking/ - - ../../linking/dashboard-links/ - - ../../linking/linking-overview/ - - ../../panels/working-with-panels/add-link-to-panel/ - - ../manage-dashboard-links/ + - ../../../dashboards/build-dashboards/manage-dashboard-links/ # /docs/grafana/next/dashboards/build-dashboards/manage-dashboard-links/ + - ../../../features/navigation-links/ # /docs/grafana/next/features/navigation-links/ + - ../../../linking/ # /docs/grafana/next/linking/ + - ../../../linking/dashboard-links/ # /docs/grafana/next/linking/dashboard-links/ + - ../../../linking/linking-overview/ # /docs/grafana/next/linking/linking-overview/ + - ../../../panels/working-with-panels/add-link-to-panel/ # /docs/grafana/next/panels/working-with-panels/add-link-to-panel + - ../../../dashboards/manage-dashboard-links/ # /docs/grafana/next/dashboards/manage-dashboard-links/ description: Add links to your Grafana dashboards to connect to other dashboards, panels, and websites keywords: - link diff --git a/docs/sources/dashboards/build-dashboards/manage-library-panels/index.md b/docs/sources/visualizations/dashboards/build-dashboards/manage-library-panels/index.md similarity index 85% rename from docs/sources/dashboards/build-dashboards/manage-library-panels/index.md rename to docs/sources/visualizations/dashboards/build-dashboards/manage-library-panels/index.md index facbdfa92a2..d8b60fef667 100644 --- a/docs/sources/dashboards/build-dashboards/manage-library-panels/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/manage-library-panels/index.md @@ -1,13 +1,14 @@ --- aliases: - - ../../panels/library-panels/ - - ../../panels/library-panels/add-library-panel/ - - ../../panels/library-panels/create-library-panel/ - - ../../panels/library-panels/delete-library-panel/ - - ../../panels/library-panels/manage-library-panel/ - - ../../panels/library-panels/unlink-library-panel/ - - ../../panels/panel-library/ - - ../manage-library-panels/ + - ../../../dashboards/build-dashboards/manage-library-panels/ # /docs/grafana/next/dashboards/build-dashboards/manage-library-panels/ + - ../../../panels/library-panels/ # /docs/grafana/next/panels/library-panels + - ../../../panels/library-panels/add-library-panel/ # /docs/grafana/next/panels/library-panels/add-library-panel + - ../../../panels/library-panels/create-library-panel/ # /docs/grafana/next/panels/library-panels/create-library-panel + - ../../../panels/library-panels/delete-library-panel/ # /docs/grafana/next/panels/library-panels/delete-library-panel + - ../../../panels/library-panels/manage-library-panel/ # /docs/grafana/next/panels/library-panels/manage-library-panel + - ../../../panels/library-panels/unlink-library-panel/ # /docs/grafana/next/panels/library-panels/unlink-library-panel + - ../../../panels/panel-library/ # /docs/grafana/next/panels/panels-library/ + - ../manage-library-panels/ # /docs/grafana/next/visualizations/dashboards/manage-library-panels/ labels: products: - cloud diff --git a/docs/sources/dashboards/build-dashboards/manage-version-history/index.md b/docs/sources/visualizations/dashboards/build-dashboards/manage-version-history/index.md similarity index 88% rename from docs/sources/dashboards/build-dashboards/manage-version-history/index.md rename to docs/sources/visualizations/dashboards/build-dashboards/manage-version-history/index.md index d1ed28efc3f..712cb4c4205 100644 --- a/docs/sources/dashboards/build-dashboards/manage-version-history/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/manage-version-history/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../reference/dashboard_history/ - - ../dashboard-history/ + - ../../../reference/dashboard_history/ # /docs/grafana/next/reference/dashboard_history/ + - ../../../dashboards/dashboard-history/ # /docs/grafana/next/dashboards/dashboard-history/ + - ../../../dashboards/build-dashboards/manage-version-history/ # /docs/grafana/next/dashboards/build-dashboards/manage-version-history/ keywords: - grafana - dashboard diff --git a/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md b/docs/sources/visualizations/dashboards/build-dashboards/modify-dashboard-settings/index.md similarity index 98% rename from docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md rename to docs/sources/visualizations/dashboards/build-dashboards/modify-dashboard-settings/index.md index dbe77c05013..dbc49c40bde 100644 --- a/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/modify-dashboard-settings/index.md @@ -34,6 +34,8 @@ refs: destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/build-dashboards/manage-dashboard-links/#dashboard-links - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/visualizations/dashboards/build-dashboards/manage-dashboard-links/#dashboard-links +aliases: + - ../../../dashboards/build-dashboards/modify-dashboard-settings/ # /docs/grafana/next/dashboards/build-dashboards/modify-dashboard-settings/ --- # Modify dashboard settings diff --git a/docs/sources/dashboards/build-dashboards/view-dashboard-json-model/index.md b/docs/sources/visualizations/dashboards/build-dashboards/view-dashboard-json-model/index.md similarity index 97% rename from docs/sources/dashboards/build-dashboards/view-dashboard-json-model/index.md rename to docs/sources/visualizations/dashboards/build-dashboards/view-dashboard-json-model/index.md index c2e95bbba00..7720a4c946a 100644 --- a/docs/sources/dashboards/build-dashboards/view-dashboard-json-model/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/view-dashboard-json-model/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../reference/dashboard/ - - ../json-model/ + - ../../../reference/dashboard/ # /docs/grafana/next/reference/dashboard/ + - ../../../dashboards/json-model/ # /docs/grafana/next/dashboards/json-model/ + - ../../../dashboards/build-dashboards/view-dashboard-json-model/ # /docs/grafana/next/dashboards/build-dashboards/view-dashboard-json-model/ keywords: - grafana - dashboard diff --git a/docs/sources/dashboards/create-manage-playlists/index.md b/docs/sources/visualizations/dashboards/create-manage-playlists/index.md similarity index 96% rename from docs/sources/dashboards/create-manage-playlists/index.md rename to docs/sources/visualizations/dashboards/create-manage-playlists/index.md index 4f2d7831350..a61238e029d 100644 --- a/docs/sources/dashboards/create-manage-playlists/index.md +++ b/docs/sources/visualizations/dashboards/create-manage-playlists/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../reference/playlist/ - - playlist/ + - ../../reference/playlist/ # /docs/grafana/next/reference/playlist/ + - ../../dashboards/playlist/ # /docs/grafana/next/dashboards/playlist/ + - ../../dashboards/create-manage-playlists/ # /docs/grafana/next/dashboards/create-manage-playlists/ keywords: - grafana - dashboard diff --git a/docs/sources/dashboards/create-reports/_index.md b/docs/sources/visualizations/dashboards/create-reports/_index.md similarity index 97% rename from docs/sources/dashboards/create-reports/_index.md rename to docs/sources/visualizations/dashboards/create-reports/_index.md index 0e9650f35ca..e55fd9e9fee 100644 --- a/docs/sources/dashboards/create-reports/_index.md +++ b/docs/sources/visualizations/dashboards/create-reports/_index.md @@ -1,10 +1,11 @@ --- aliases: - - ../administration/reports/ # /docs/grafana/latest/administration/reports/ - - ../enterprise/export-pdf/ # /docs/grafana/latest/enterprise/export-pdf/ - - ../enterprise/reporting/ # /docs/grafana/latest/enterprise/reporting/ - - ../panels/create-reports/ # /docs/grafana/latest/panels/create-reports/ - - reporting/ # /docs/grafana/latest/dashboards/reporting/ + - ../../administration/reports/ # /docs/grafana/next/administration/reports/ + - ../../enterprise/export-pdf/ # /docs/grafana/next/enterprise/export-pdf/ + - ../../enterprise/reporting/ # /docs/grafana/next/enterprise/reporting/ + - ../../panels/create-reports/ # /docs/grafana/next/panels/create-reports/ + - ../../dashboards/reporting/ # /docs/grafana/next/dashboards/reporting/ + - ../../dashboards/create-reports/ # /docs/grafana/next/dashboards/create-reports/ keywords: - grafana - reporting diff --git a/docs/sources/dashboards/create-reports/report-settings/index.md b/docs/sources/visualizations/dashboards/create-reports/report-settings/index.md similarity index 96% rename from docs/sources/dashboards/create-reports/report-settings/index.md rename to docs/sources/visualizations/dashboards/create-reports/report-settings/index.md index b63b636d809..6f131c676a2 100644 --- a/docs/sources/dashboards/create-reports/report-settings/index.md +++ b/docs/sources/visualizations/dashboards/create-reports/report-settings/index.md @@ -17,6 +17,8 @@ refs: destination: /docs/grafana/<GRAFANA_VERSION>/administration/organization-preferences/#change-grafana-ui-theme - pattern: /docs/grafana-cloud/ destination: /docs/grafana/<GRAFANA_VERSION>/administration/organization-preferences/#change-grafana-ui-theme +aliases: + - ../../../dashboards/create-reports/report-settings/ # /docs/grafana/next/dashboards/create-reports/report-settings/ --- # Reporting settings diff --git a/docs/sources/dashboards/manage-dashboards/index.md b/docs/sources/visualizations/dashboards/manage-dashboards/index.md similarity index 92% rename from docs/sources/dashboards/manage-dashboards/index.md rename to docs/sources/visualizations/dashboards/manage-dashboards/index.md index a5473c5a71f..83052314134 100644 --- a/docs/sources/dashboards/manage-dashboards/index.md +++ b/docs/sources/visualizations/dashboards/manage-dashboards/index.md @@ -1,9 +1,10 @@ --- aliases: - - ../panels/working-with-panels/organize-dashboard/ - - ../reference/dashboard_folders/ - - dashboard-folders/ - - dashboard-manage/ + - ../../panels/working-with-panels/organize-dashboard/ # /docs/grafana/next/panels/working-with-panels/organize-dashboard/ + - ../../reference/dashboard_folders/ # /docs/grafana/next/reference/dashboard_folders/ + - ../../dashboards/dashboard-folders/ # /docs/grafana/next/dashboards/dashboard-folders/ + - ../../dashboards/dashboard-manage/ # /docs/grafana/next/dashboards/dashboard-manage/ + - ../../dashboards/manage-dashboards/ # /docs/grafana/next/dashboards/manage-dashboards/ canonical: https://grafana.com/docs/grafana/latest/dashboards/manage-dashboards/ keywords: - grafana diff --git a/docs/sources/dashboards/search-dashboards/index.md b/docs/sources/visualizations/dashboards/search-dashboards/index.md similarity index 98% rename from docs/sources/dashboards/search-dashboards/index.md rename to docs/sources/visualizations/dashboards/search-dashboards/index.md index 61126b9828c..63b3804fdeb 100644 --- a/docs/sources/dashboards/search-dashboards/index.md +++ b/docs/sources/visualizations/dashboards/search-dashboards/index.md @@ -28,6 +28,8 @@ refs: destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#feature_toggles - pattern: /docs/grafana-cloud/ destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/#feature_toggles +aliases: + - ../../dashboards/search-dashboards/ # /docs/grafana/next/dashboards/search-dashboards/ --- # Search dashboards and folders diff --git a/docs/sources/dashboards/share-dashboards-panels/_index.md b/docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md similarity index 96% rename from docs/sources/dashboards/share-dashboards-panels/_index.md rename to docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md index 1b606ed8d0d..3454f719a0c 100644 --- a/docs/sources/dashboards/share-dashboards-panels/_index.md +++ b/docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md @@ -1,13 +1,14 @@ --- aliases: - - ../reference/share_dashboard/ # /docs/grafana/latest/reference/share_dashboard/ - - ../reference/share_panel/ # /docs/grafana/latest/reference/share_panel/ - - ../share-dashboards-panels/ # /docs/grafana/latest/share-dashboards-panels/ - - ../sharing/ # /docs/grafana/latest/sharing/ - - ../sharing/playlists/ # /docs/grafana/latest/sharing/playlists/ - - ../sharing/share-dashboard/ # /docs/grafana/latest/sharing/share-dashboard/ - - ../sharing/share-panel/ # /docs/grafana/latest/sharing/share-panel/ - - share-dashboard/ # /docs/grafana/latest/dashboards/share-dashboard/ + - ../../reference/share_dashboard/ # /docs/grafana/next/reference/share_dashboard/ + - ../../reference/share_panel/ # /docs/grafana/next/reference/share_panel/ + - ../../share-dashboards-panels/ # /docs/grafana/next/share-dashboards-panels/ + - ../../sharing/ # /docs/grafana/next/sharing/ + - ../../sharing/playlists/ # /docs/grafana/next/sharing/playlists/ + - ../../sharing/share-dashboard/ # /docs/grafana/next/sharing/share-dashboard/ + - ../../sharing/share-panel/ # /docs/grafana/next/sharing/share-panel/ + - ../../dashboards/share-dashboard/ # /docs/grafana/next/dashboards/share-dashboard/ + - ../../dashboards/share-dashboards-panels/ # /docs/grafana/next/dashboards/share-dashboards-panels/ keywords: - grafana - dashboard diff --git a/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md b/docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md similarity index 98% rename from docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md rename to docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md index 9ea6df4e47e..dc4b41ce1fd 100644 --- a/docs/sources/dashboards/share-dashboards-panels/shared-dashboards/index.md +++ b/docs/sources/visualizations/dashboards/share-dashboards-panels/shared-dashboards/index.md @@ -1,6 +1,7 @@ --- aliases: - - ../dashboard-public/ # /docs/grafana/latest/dashboards/dashboard-public/ + - ../../../dashboards/dashboard-public/ # /docs/grafana/next/dashboards/dashboard-public/ + - ../../../dashboards/share-dashboards-panels/shared-dashboards/ # /docs/grafana/next/dashboards/share-dashboards-panels/shared-dashboards/ labels: products: - cloud diff --git a/docs/sources/dashboards/troubleshoot-dashboards/index.md b/docs/sources/visualizations/dashboards/troubleshoot-dashboards/index.md similarity index 91% rename from docs/sources/dashboards/troubleshoot-dashboards/index.md rename to docs/sources/visualizations/dashboards/troubleshoot-dashboards/index.md index bb6530b7952..c04defe36c4 100644 --- a/docs/sources/dashboards/troubleshoot-dashboards/index.md +++ b/docs/sources/visualizations/dashboards/troubleshoot-dashboards/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../troubleshooting/troubleshoot-dashboards/ - - ../reference/timerange/ + - ../../troubleshooting/troubleshoot-dashboards/ # /docs/grafana/next/troubleshooting/troubleshoot-dashboards/ + - ../../reference/timerange/ # /docs/grafana/next/reference/timerange/ + - ../../dashboards/troubleshoot-dashboards/ # /docs/grafana/next/dashboards/troubleshoot-dashboards/ canonical: https://grafana.com/docs/grafana/latest/dashboards/troubleshoot-dashboards/ keywords: - grafana diff --git a/docs/sources/dashboards/use-dashboards/index.md b/docs/sources/visualizations/dashboards/use-dashboards/index.md similarity index 95% rename from docs/sources/dashboards/use-dashboards/index.md rename to docs/sources/visualizations/dashboards/use-dashboards/index.md index 5d956b4c380..108d6bc8136 100644 --- a/docs/sources/dashboards/use-dashboards/index.md +++ b/docs/sources/visualizations/dashboards/use-dashboards/index.md @@ -1,12 +1,13 @@ --- aliases: - - ../reference/search/ - - dashboard-ui/ - - dashboard-ui/dashboard-header/ - - dashboard-ui/dashboard-row/ - - search/ - - shortcuts/ - - time-range-controls/ + - ../../reference/search/ # /docs/grafana/next/reference/search/ + - ../../dashboards/dashboard-ui/ # /docs/grafana/next/dashboards/dashboard-ui/ + - ../../dashboards/dashboard-ui/dashboard-header/ # /docs/grafana/next/dashboards/dashboard-ui/dashboard-header/ + - ../../dashboards/dashboard-ui/dashboard-row/ # /docs/grafana/next/dashboards/dashboard-ui/dashboard-row/ + - ../../dashboards/search/ # /docs/grafana/next/dashboards/search/ + - ../../dashboards/shortcuts/ # /docs/grafana/next/dashboards/shortcuts/ + - ../../dashboards/time-range-controls/ # /docs/grafana/next/dashboards/time-range-controls/ + - ../../dashboards/use-dashboards/ # /docs/grafana/next/dashboards/use-dashboards/ keywords: - dashboard - search diff --git a/docs/sources/dashboards/variables/_index.md b/docs/sources/visualizations/dashboards/variables/_index.md similarity index 93% rename from docs/sources/dashboards/variables/_index.md rename to docs/sources/visualizations/dashboards/variables/_index.md index da7451271d2..df11c5d64ac 100644 --- a/docs/sources/dashboards/variables/_index.md +++ b/docs/sources/visualizations/dashboards/variables/_index.md @@ -1,8 +1,9 @@ --- aliases: - - ../variables/ # /docs/grafana/<GRAFANA VERSION>/variables/ - - ../variables/templates-and-variables/ # /docs/grafana/<GRAFANA VERSION>/variables/templates-and-variables/ - - ../variables/variable-examples/ # /docs/grafana/<GRAFANA VERSION>/variables/variable-examples/ + - ../../variables/ # /docs/grafana/next/variables/ + - ../../variables/templates-and-variables/ # /docs/grafana/next/variables/templates-and-variables/ + - ../../variables/variable-examples/ # /docs/grafana/next/variables/variable-examples/ + - ../../dashboards/variables/ # /docs/grafana/next/dashboards/variables/ labels: products: - cloud diff --git a/docs/sources/dashboards/variables/add-template-variables/index.md b/docs/sources/visualizations/dashboards/variables/add-template-variables/index.md similarity index 94% rename from docs/sources/dashboards/variables/add-template-variables/index.md rename to docs/sources/visualizations/dashboards/variables/add-template-variables/index.md index b41615cd4e7..0343d78fb91 100644 --- a/docs/sources/dashboards/variables/add-template-variables/index.md +++ b/docs/sources/visualizations/dashboards/variables/add-template-variables/index.md @@ -1,30 +1,31 @@ --- aliases: - - ../../reference/templating/ - - ../../variables/add-ad-hoc-filters/ - - ../../variables/add-constant-variable/ - - ../../variables/add-custom-variable/ - - ../../variables/add-data-source-variable/ - - ../../variables/add-interval-variable/ - - ../../variables/add-query-variable/ - - ../../variables/add-template-variables/ - - ../../variables/add-text-box-variable/ - - ../../variables/chained-variables/ - - ../../variables/filter-variables-with-regex/ - - ../../variables/formatting-multi-value-variables/ - - ../../variables/global-variables/ - - ../../variables/manage-variable/ - - ../../variables/variable-selection-options/ - - ../../variables/variable-types/ - - ../../variables/variable-types/add-ad-hoc-filters/ - - ../../variables/variable-types/add-constant-variable/ - - ../../variables/variable-types/add-custom-variable/ - - ../../variables/variable-types/add-data-source-variable/ - - ../../variables/variable-types/add-interval-variable/ - - ../../variables/variable-types/add-query-variable/ - - ../../variables/variable-types/add-text-box-variable/ - - ../../variables/variable-types/chained-variables/ - - ../../variables/variable-types/global-variables/ + - ../../../reference/templating/ # /docs/grafana/next/reference/templating/ + - ../../../variables/add-ad-hoc-filters/ # /docs/grafana/next/variables/add-ad-hoc-filters/ + - ../../../variables/add-constant-variable/ # /docs/grafana/next/variables/add-constant-variable/ + - ../../../variables/add-custom-variable/ # /docs/grafana/next/variables/add-custom-variable/ + - ../../../variables/add-data-source-variable/ # /docs/grafana/next/variables/add-data-source-variable/ + - ../../../variables/add-interval-variable/ # /docs/grafana/next/variables/add-interval-variable/ + - ../../../variables/add-query-variable/ # /docs/grafana/next/variables/add-query-variable/ + - ../../../variables/add-template-variables/ # /docs/grafana/next/variables/add-template-variables/ + - ../../../variables/add-text-box-variable/ # /docs/grafana/next/variables/add-text-box-variable/ + - ../../../variables/chained-variables/ # /docs/grafana/next/variables/chained-variables/ + - ../../../variables/filter-variables-with-regex/ # /docs/grafana/next/variables/filter-variables-with-regex/ + - ../../../variables/formatting-multi-value-variables/ # /docs/grafana/next/variables/formatting-multi-value-variables/ + - ../../../variables/global-variables/ # /docs/grafana/next/variables/global-variables/ + - ../../../variables/manage-variable/ # /docs/grafana/next/variables/manage-variable/ + - ../../../variables/variable-selection-options/ # /docs/grafana/next/variables/variable-selection-options/ + - ../../../variables/variable-types/ # /docs/grafana/next/variables/variable-types/ + - ../../../variables/variable-types/add-ad-hoc-filters/ # /docs/grafana/next/variables/variable-types/add-ad-hoc-filters/ + - ../../../variables/variable-types/add-constant-variable/ # /docs/grafana/next/variables/variable-types/add-constant-variable/ + - ../../../variables/variable-types/add-custom-variable/ # /docs/grafana/next/variables/variable-types/add-custom-variable/ + - ../../../variables/variable-types/add-data-source-variable/ # /docs/grafana/next/variables/variable-types/add-data-source-variable/ + - ../../../variables/variable-types/add-interval-variable/ # /docs/grafana/next/variables/variable-types/add-interval-variable/ + - ../../../variables/variable-types/add-query-variable/ # /docs/grafana/next/variables/variable-types/add-query-variable/ + - ../../../variables/variable-types/add-text-box-variable/ # /docs/grafana/next/variables/variable-types/add-text-box-variable/ + - ../../../variables/variable-types/chained-variables/ # /docs/grafana/next/variables/variable-types/chained-variables/ + - ../../../variables/variable-types/global-variables/ # /docs/grafana/next/variables/variable-types/global-variables/ + - ../../../dashboards/variables/add-template-variables/ # /docs/grafana/next/dashboards/variables/add-template-variables/ keywords: - grafana - documentation diff --git a/docs/sources/dashboards/variables/inspect-variable/index.md b/docs/sources/visualizations/dashboards/variables/inspect-variable/index.md similarity index 90% rename from docs/sources/dashboards/variables/inspect-variable/index.md rename to docs/sources/visualizations/dashboards/variables/inspect-variable/index.md index bcdb8635141..8466a4228ce 100644 --- a/docs/sources/dashboards/variables/inspect-variable/index.md +++ b/docs/sources/visualizations/dashboards/variables/inspect-variable/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../reference/templating/ - - ../../variables/inspect-variable/ + - ../../../reference/templating/ # /docs/grafana/next/reference/templating/ + - ../../../variables/inspect-variable/ # /docs/grafana/next/variables/inspect-variable/ + - ../../../dashboards/variables/inspect-variable/ # /docs/grafana/next/dashboards/variables/inspect-variable/ keywords: - grafana - templating diff --git a/docs/sources/dashboards/variables/variable-syntax/index.md b/docs/sources/visualizations/dashboards/variables/variable-syntax/index.md similarity index 94% rename from docs/sources/dashboards/variables/variable-syntax/index.md rename to docs/sources/visualizations/dashboards/variables/variable-syntax/index.md index 7d0fbc81993..2c0b4060921 100644 --- a/docs/sources/dashboards/variables/variable-syntax/index.md +++ b/docs/sources/visualizations/dashboards/variables/variable-syntax/index.md @@ -1,8 +1,9 @@ --- aliases: - - ../../reference/templating/ - - ../../variables/advanced-variable-format-options/ - - ../../variables/syntax/ + - ../../../reference/templating/ # /docs/grafana/next/reference/templating/ + - ../../../variables/advanced-variable-format-options/ # /docs/grafana/next/variables/advanced-variable-format-options/ + - ../../../variables/syntax/ # /docs/grafana/next/variables/syntax/ + - ../../../dashboards/variables/variable-syntax/ # /docs/grafana/next/dashboards/variables/variable-syntax/ keywords: - grafana - templating diff --git a/docs/sources/explore/_index.md b/docs/sources/visualizations/explore/_index.md similarity index 91% rename from docs/sources/explore/_index.md rename to docs/sources/visualizations/explore/_index.md index 0deda3011d3..3ef9659a165 100644 --- a/docs/sources/explore/_index.md +++ b/docs/sources/visualizations/explore/_index.md @@ -1,6 +1,8 @@ --- aliases: - - features/explore/ + - ../features/explore/ # /docs/grafana/next/features/explore/ + - ../explore/ # /docs/grafana/next/explore/ + - /docs/grafana-cloud/visualizations/explore/ keywords: - explore - loki diff --git a/docs/sources/explore/correlations-editor-in-explore.md b/docs/sources/visualizations/explore/correlations-editor-in-explore.md similarity index 98% rename from docs/sources/explore/correlations-editor-in-explore.md rename to docs/sources/visualizations/explore/correlations-editor-in-explore.md index 7f93fd30dc6..fde5d933103 100644 --- a/docs/sources/explore/correlations-editor-in-explore.md +++ b/docs/sources/visualizations/explore/correlations-editor-in-explore.md @@ -6,6 +6,8 @@ labels: - cloud title: Correlations Editor in Explore weight: 20 +aliases: + - ../../explore/correlations-editor-in-explore/ # /docs/grafana/next/explore/correlations-editor-in-explore/ --- # Correlations Editor in Explore diff --git a/docs/sources/explore/explore-inspector.md b/docs/sources/visualizations/explore/explore-inspector.md similarity index 96% rename from docs/sources/explore/explore-inspector.md rename to docs/sources/visualizations/explore/explore-inspector.md index 6880f292e82..17074a21fab 100644 --- a/docs/sources/explore/explore-inspector.md +++ b/docs/sources/visualizations/explore/explore-inspector.md @@ -9,6 +9,9 @@ keywords: - Explore title: Query inspector in Explore weight: 15 +aliases: + - ../../explore/explore-inspector/ # /docs/grafana/next/explore/explore-inspector/ + - /docs/grafana-cloud/visualizations/explore/explore-inspector/ --- # Query inspector in Explore diff --git a/docs/sources/explore/get-started-with-explore.md b/docs/sources/visualizations/explore/get-started-with-explore.md similarity index 99% rename from docs/sources/explore/get-started-with-explore.md rename to docs/sources/visualizations/explore/get-started-with-explore.md index 5b7a97b85d9..8874741cb44 100644 --- a/docs/sources/explore/get-started-with-explore.md +++ b/docs/sources/visualizations/explore/get-started-with-explore.md @@ -1,6 +1,6 @@ --- aliases: - - + - ../../explore/get-started-with-explore/ # /docs/grafana/next/explore/get-started-with-explore/ keywords: - explore - loki diff --git a/docs/sources/explore/logs-integration.md b/docs/sources/visualizations/explore/logs-integration.md similarity index 99% rename from docs/sources/explore/logs-integration.md rename to docs/sources/visualizations/explore/logs-integration.md index 42af2ccc0e0..8aa18f45616 100644 --- a/docs/sources/explore/logs-integration.md +++ b/docs/sources/visualizations/explore/logs-integration.md @@ -10,6 +10,8 @@ labels: - oss title: Logs in Explore weight: 25 +aliases: + - ../../explore/logs-integration/ # /docs/grafana/next/explore/logs-integration/ --- # Logs in Explore diff --git a/docs/sources/explore/query-management.md b/docs/sources/visualizations/explore/query-management.md similarity index 97% rename from docs/sources/explore/query-management.md rename to docs/sources/visualizations/explore/query-management.md index 11f86be67c9..4be1a932ea8 100644 --- a/docs/sources/explore/query-management.md +++ b/docs/sources/visualizations/explore/query-management.md @@ -16,6 +16,8 @@ refs: destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/query-transform-data/#saved-queries - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#saved-queries +aliases: + - ../../explore/query-management/ # /docs/grafana/next/explore/query-management/ --- # Query management in Explore diff --git a/docs/sources/explore/trace-integration.md b/docs/sources/visualizations/explore/trace-integration.md similarity index 99% rename from docs/sources/explore/trace-integration.md rename to docs/sources/visualizations/explore/trace-integration.md index e1675c3d414..9a07f6ad18c 100644 --- a/docs/sources/explore/trace-integration.md +++ b/docs/sources/visualizations/explore/trace-integration.md @@ -9,6 +9,8 @@ labels: - oss title: Traces in Explore weight: 40 +aliases: + - ../../explore/trace-integration/ # /docs/grafana/next/explore/trace-integration/ --- # Traces in Explore diff --git a/docs/sources/panels-visualizations/_index.md b/docs/sources/visualizations/panels-visualizations/_index.md similarity index 89% rename from docs/sources/panels-visualizations/_index.md rename to docs/sources/visualizations/panels-visualizations/_index.md index d6f2e0982e8..642c1ba183b 100644 --- a/docs/sources/panels-visualizations/_index.md +++ b/docs/sources/visualizations/panels-visualizations/_index.md @@ -1,8 +1,9 @@ --- aliases: - - dashboards/configure-panels-visualizations/ - - features/panels/panels/ - - panels/ + - ../dashboards/configure-panels-visualizations/ # /docs/grafana/next/dashboards/configure-panels-visualizations/ + - ../features/panels/panels/ # /docs/grafana/next/features/panels/panels/ + - ../panels/ # /docs/grafana/next/panels/ + - ../panels-visualizations/ # /docs/grafana/next/panels-visualizations/ keywords: - grafana - configure diff --git a/docs/sources/panels-visualizations/configure-data-links/index.md b/docs/sources/visualizations/panels-visualizations/configure-data-links/index.md similarity index 96% rename from docs/sources/panels-visualizations/configure-data-links/index.md rename to docs/sources/visualizations/panels-visualizations/configure-data-links/index.md index ad1724acb09..7d72bcba934 100644 --- a/docs/sources/panels-visualizations/configure-data-links/index.md +++ b/docs/sources/visualizations/panels-visualizations/configure-data-links/index.md @@ -1,11 +1,12 @@ --- aliases: - - ../linking/data-link-variables/ - - ../linking/data-links/ - - ../panels/configure-data-links/ - - ../reference/datalinks/ - - ../variables/url-variables/ - - ../variables/variable-types/url-variables/ + - ../../linking/data-link-variables/ # /docs/grafana/next/linking/data-link-variables/ + - ../../linking/data-links/ # /docs/grafana/next/linking/data-links/ + - ../../panels/configure-data-links/ # /docs/grafana/next/panels/configure-data-links/ + - ../../reference/datalinks/ # /docs/grafana/next/reference/datalinks/ + - ../../variables/url-variables/ # /docs/grafana/next/variables/url-variables/ + - ../../variables/variable-types/url-variables/ # /docs/grafana/next/variables/variable-types/url-variables/ + - ../../panels-visualizations/configure-data-links/ # /docs/grafana/next/panels-visualizations/configure-data-links/ keywords: - grafana - url variables diff --git a/docs/sources/panels-visualizations/configure-legend/index.md b/docs/sources/visualizations/panels-visualizations/configure-legend/index.md similarity index 95% rename from docs/sources/panels-visualizations/configure-legend/index.md rename to docs/sources/visualizations/panels-visualizations/configure-legend/index.md index 4f7a6e27648..8c8baa81800 100644 --- a/docs/sources/panels-visualizations/configure-legend/index.md +++ b/docs/sources/visualizations/panels-visualizations/configure-legend/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../panels/working-with-panels/configure-legend/ - - visualizations/configure-legend/ + - ../../panels/working-with-panels/configure-legend/ # /docs/grafana/next/panels/working-with-panels/configure-legend/ + - ../../panels-visualizations/visualizations/configure-legend/ # /docs/grafana/next/panels-visualizations/visualizations/configure-legend/ + - ../../panels-visualizations/configure-legend/ # /docs/grafana/next/panels-visualizations/configure-legend/ labels: products: - cloud diff --git a/docs/sources/panels-visualizations/configure-overrides/index.md b/docs/sources/visualizations/panels-visualizations/configure-overrides/index.md similarity index 92% rename from docs/sources/panels-visualizations/configure-overrides/index.md rename to docs/sources/visualizations/panels-visualizations/configure-overrides/index.md index c01a72bd781..c46223ac73b 100644 --- a/docs/sources/panels-visualizations/configure-overrides/index.md +++ b/docs/sources/visualizations/panels-visualizations/configure-overrides/index.md @@ -1,13 +1,14 @@ --- aliases: - - ../panels/configure-overrides/ - - ../panels/field-overrides/ - - ../panels/override-field-values/ - - ../panels/override-field-values/about-field-overrides/ - - ../panels/override-field-values/add-a-field-override/ - - ../panels/override-field-values/delete-a-field-override/ - - ../panels/override-field-values/edit-field-override/ - - ../panels/override-field-values/view-field-override/ + - ../../panels/configure-overrides/ # /docs/grafana/next/panels/configure-overrides/ + - ../../panels/field-overrides/ # /docs/grafana/next/panels/field-overrides/ + - ../../panels/override-field-values/ # /docs/grafana/next/panels/override-field-values/ + - ../../panels/override-field-values/about-field-overrides/ # /docs/grafana/next/panels/override-field-values/about-field-overrides/ + - ../../panels/override-field-values/add-a-field-override/ # /docs/grafana/next/panels/override-field-values/add-a-field-override/ + - ../../panels/override-field-values/delete-a-field-override/ # /docs/grafana/next/panels/override-field-values/delete-a-field-override/ + - ../../panels/override-field-values/edit-field-override/ # /docs/grafana/next/panels/override-field-values/edit-field-override/ + - ../../panels/override-field-values/view-field-override/ # /docs/grafana/next/panels/override-field-values/view-field-override/ + - ../../panels-visualizations/configure-overrides/ # /docs/grafana/next/panels-visualizations/configure-overrides/ labels: products: - cloud diff --git a/docs/sources/panels-visualizations/configure-panel-options/index.md b/docs/sources/visualizations/panels-visualizations/configure-panel-options/index.md similarity index 91% rename from docs/sources/panels-visualizations/configure-panel-options/index.md rename to docs/sources/visualizations/panels-visualizations/configure-panel-options/index.md index dc7c8601c11..3a2185d699a 100644 --- a/docs/sources/panels-visualizations/configure-panel-options/index.md +++ b/docs/sources/visualizations/panels-visualizations/configure-panel-options/index.md @@ -1,10 +1,11 @@ --- aliases: - - ../panels/add-panels-dynamically/ - - ../panels/configure-panel-options/ - - ../panels/repeat-panels-or-rows/ - - ../panels/working-with-panels/add-title-and-description/ - - ../panels/working-with-panels/view-json-model/ + - ../../panels/add-panels-dynamically/ # /docs/grafana/next/panels/add-panels-dynamically/ + - ../../panels/configure-panel-options/ # /docs/grafana/next/panels/configure-panel-options/ + - ../../panels/repeat-panels-or-rows/ # /docs/grafana/next/panels/repeat-panels-or-rows/ + - ../../panels/working-with-panels/add-title-and-description/ # /docs/grafana/next/panels/working-with-panels/add-title-and-description/ + - ../../panels/working-with-panels/view-json-model/ # /docs/grafana/next/panels/working-with-panels/view-json-model/ + - ../../panels-visualizations/configure-panel-options/ # /docs/grafana/next/panels-visualizations/configure-panel-options/ keywords: - panel - dynamic diff --git a/docs/sources/panels-visualizations/configure-standard-options/index.md b/docs/sources/visualizations/panels-visualizations/configure-standard-options/index.md similarity index 95% rename from docs/sources/panels-visualizations/configure-standard-options/index.md rename to docs/sources/visualizations/panels-visualizations/configure-standard-options/index.md index c4ed1caa981..ca35f5df71c 100644 --- a/docs/sources/panels-visualizations/configure-standard-options/index.md +++ b/docs/sources/visualizations/panels-visualizations/configure-standard-options/index.md @@ -1,13 +1,14 @@ --- aliases: - - ../panels/apply-color-to-series/ - - ../panels/configure-standard-options/ - - ../panels/field-options/ - - ../panels/field-options/standard-field-options/ - - ../panels/reference-standard-field-definitions/ - - ../panels/standard-field-definitions/ - - ../panels/working-with-panels/format-standard-fields/ - - ../panels/field-configuration-options/ # /docs/grafana/latest/panels/field-configuration-options/ + - ../../panels/apply-color-to-series/ # /docs/grafana/next/panels/apply-color-to-series/ + - ../../panels/configure-standard-options/ # /docs/grafana/next/panels/configure-standard-options/ + - ../../panels/field-options/ # /docs/grafana/next/panels/field-options/ + - ../../panels/field-options/standard-field-options/ # /docs/grafana/next/panels/field-options/standard-field-options/ + - ../../panels/reference-standard-field-definitions/ # /docs/grafana/next/panels/reference-standard-field-definitions/ + - ../../panels/standard-field-definitions/ # /docs/grafana/next/panels/standard-field-definitions/ + - ../../panels/working-with-panels/format-standard-fields/ # /docs/grafana/next/panels/working-with-panels/format-standard-fields/ + - ../../panels/field-configuration-options/ # /docs/grafana/next/panels/field-configuration-options/ + - ../../panels-visualizations/configure-standard-options/ # /docs/grafana/next/panels-visualizations/configure-standard-options/ keywords: - panel - dashboard diff --git a/docs/sources/panels-visualizations/configure-thresholds/index.md b/docs/sources/visualizations/panels-visualizations/configure-thresholds/index.md similarity index 92% rename from docs/sources/panels-visualizations/configure-thresholds/index.md rename to docs/sources/visualizations/panels-visualizations/configure-thresholds/index.md index cf0805a5e93..d4ebc5c123c 100644 --- a/docs/sources/panels-visualizations/configure-thresholds/index.md +++ b/docs/sources/visualizations/panels-visualizations/configure-thresholds/index.md @@ -1,12 +1,13 @@ --- aliases: - - ../panels/ - - ../panels/configure-thresholds/ - - ../panels/specify-thresholds/about-thresholds/ - - ../panels/specify-thresholds/add-a-threshold/ - - ../panels/specify-thresholds/add-threshold-to-graph/ - - ../panels/specify-thresholds/delete-a-threshold/ - - ../panels/thresholds/ + - ../../panels/ # /docs/grafana/next/panels/ + - ../../panels/configure-thresholds/ # /docs/grafana/next/panels/configure-thresholds/ + - ../../panels/specify-thresholds/about-thresholds/ # /docs/grafana/next/panels/specify-thresholds/about-thresholds/ + - ../../panels/specify-thresholds/add-a-threshold/ # /docs/grafana/next/panels/specify-thresholds/add-a-threshold/ + - ../../panels/specify-thresholds/add-threshold-to-graph/ # /docs/grafana/next/panels/specify-thresholds/add-threshold-to-graph/ + - ../../panels/specify-thresholds/delete-a-threshold/ # /docs/grafana/next/panels/specify-thresholds/delete-a-threshold/ + - ../../panels/thresholds/ # /docs/grafana/next/panels/thresholds/ + - ../../panels-visualizations/configure-thresholds/ # /docs/grafana/next/panels-visualizations/configure-thresholds/ description: Configure thresholds in your visualizations labels: products: diff --git a/docs/sources/panels-visualizations/configure-tooltips/index.md b/docs/sources/visualizations/panels-visualizations/configure-tooltips/index.md similarity index 100% rename from docs/sources/panels-visualizations/configure-tooltips/index.md rename to docs/sources/visualizations/panels-visualizations/configure-tooltips/index.md diff --git a/docs/sources/panels-visualizations/configure-value-mappings/index.md b/docs/sources/visualizations/panels-visualizations/configure-value-mappings/index.md similarity index 90% rename from docs/sources/panels-visualizations/configure-value-mappings/index.md rename to docs/sources/visualizations/panels-visualizations/configure-value-mappings/index.md index 11223d3870b..1e8cb947a69 100644 --- a/docs/sources/panels-visualizations/configure-value-mappings/index.md +++ b/docs/sources/visualizations/panels-visualizations/configure-value-mappings/index.md @@ -1,14 +1,15 @@ --- aliases: - - ../panels/configure-value-mappings/ - - ../panels/format-data/ - - ../panels/format-data/about-value-mapping/ - - ../panels/format-data/edit-value-mapping/ - - ../panels/format-data/map-a-range/ - - ../panels/format-data/map-a-regular-expression/ - - ../panels/format-data/map-a-special-value/ - - ../panels/format-data/map-a-value/ - - ../panels/value-mappings/ + - ../../configure-value-mappings/ # /docs/grafana/next/panels/configure-value-mappings/ + - ../../panels/format-data/ # /docs/grafana/next/panels/format-data/ + - ../../panels/format-data/about-value-mapping/ # /docs/grafana/next/panels/format-data/about-value-mapping/ + - ../../panels/format-data/edit-value-mapping/ # /docs/grafana/next/panels/format-data/edit-value-mapping/ + - ../../panels/format-data/map-a-range/ # /docs/grafana/next/panels/format-data/map-a-range/ + - ../../panels/format-data/map-a-regular-expression/ # /docs/grafana/next/panels/format-data/map-a-regular-expression/ + - ../../panels/format-data/map-a-special-value/ # /docs/grafana/next/panels/format-data/map-a-special-value/ + - ../../panels/format-data/map-a-value/ # /docs/grafana/next/panels/format-data/map-a-value/ + - ../../panels/value-mappings/ # /docs/grafana/next/panels/value-mappings/ + - ../../panels-visualizations/configure-value-mappings/ # /docs/grafana/next/panels-visualizations/configure-value-mappings/ labels: products: - cloud diff --git a/docs/sources/panels-visualizations/panel-editor-overview/index.md b/docs/sources/visualizations/panels-visualizations/panel-editor-overview/index.md similarity index 77% rename from docs/sources/panels-visualizations/panel-editor-overview/index.md rename to docs/sources/visualizations/panels-visualizations/panel-editor-overview/index.md index fbde6ce7975..0a2247537cf 100644 --- a/docs/sources/panels-visualizations/panel-editor-overview/index.md +++ b/docs/sources/visualizations/panels-visualizations/panel-editor-overview/index.md @@ -1,15 +1,16 @@ --- aliases: - - ../dashboards/add-organize-panels/ - - ../dashboards/dashboard-create/ - - ../panels/add-panels-dynamically/about-repeating-panels-rows/ - - ../panels/add-panels-dynamically/configure-repeating-panels/ - - ../panels/add-panels-dynamically/configure-repeating-rows/ - - ../panels/working-with-panels/ - - ../panels/working-with-panels/add-panel/ - - ../panels/working-with-panels/navigate-inspector-panel/ - - ../panels/working-with-panels/navigate-panel-editor/ - - add-organize-panels/ + - ../../dashboards/add-organize-panels/ # /docs/grafana/next/dashboards/add-organize-panels/ + - ../../dashboards/dashboard-create/ # /docs/grafana/next/dashboards/dashboard-create/ + - ../../panels/add-panels-dynamically/about-repeating-panels-rows/ # /docs/grafana/next/panels/add-panels-dynamically/about-repeating-panels-rows/ + - ../../panels/add-panels-dynamically/configure-repeating-panels/ # /docs/grafana/next/panels/add-panels-dynamically/configure-repeating-panels/ + - ../../panels/add-panels-dynamically/configure-repeating-rows/ # /docs/grafana/next/panels/add-panels-dynamically/configure-repeating-rows/ + - ../../panels/working-with-panels/ # /docs/grafana/next/panels/working-with-panels/ + - ../../panels/working-with-panels/add-panel/ # /docs/grafana/next/panels/working-with-panels/add-panel/ + - ../../panels/working-with-panels/navigate-inspector-panel/ # /docs/grafana/next/panels/working-with-panels/navigate-inspector-panel/ + - ../../panels/working-with-panels/navigate-panel-editor/ # /docs/grafana/next/panels/working-with-panels/navigate-panel-editor/ + - ../../panels-visualizations/add-organize-panels/ # /docs/grafana/next/panels-visualizations/add-organize-panels/ + - ../../panels-visualizations/panel-editor-overview/ # /docs/grafana/next/panels-visualizations/panel-editor-overview/ keywords: - panel - dashboard diff --git a/docs/sources/panels-visualizations/panel-inspector/index.md b/docs/sources/visualizations/panels-visualizations/panel-inspector/index.md similarity index 83% rename from docs/sources/panels-visualizations/panel-inspector/index.md rename to docs/sources/visualizations/panels-visualizations/panel-inspector/index.md index d751957a171..15cfc8d7ed6 100644 --- a/docs/sources/panels-visualizations/panel-inspector/index.md +++ b/docs/sources/visualizations/panels-visualizations/panel-inspector/index.md @@ -1,9 +1,10 @@ --- aliases: - - ../panels/query-a-data-source/download-raw-query-results/ - - ../panels/query-a-data-source/inspect-query-performance/ - - ../panels/query-a-data-source/inspect-request-and-response-data/ - - ../panels/working-with-panels/navigate-inspector-panel/ + - ../../panels/query-a-data-source/download-raw-query-results/ # /docs/grafana/next/panels/query-a-data-source/download-raw-query-results/ + - ../../panels/query-a-data-source/inspect-query-performance/ # /docs/grafana/next/panels/query-a-data-source/inspect-query-performance/ + - ../../panels/query-a-data-source/inspect-request-and-response-data/ # /docs/grafana/next/panels/query-a-data-source/inspect-request-and-response-data/ + - ../../panels/working-with-panels/navigate-inspector-panel/ # /docs/grafana/next/panels/working-with-panels/navigate-inspector-panel/ + - ../../panels-visualizations/panel-inspector/ # /docs/grafana/next/panels-visualizations/panel-inspector/ labels: products: - cloud diff --git a/docs/sources/panels-visualizations/panel-overview/index.md b/docs/sources/visualizations/panels-visualizations/panel-overview/index.md similarity index 98% rename from docs/sources/panels-visualizations/panel-overview/index.md rename to docs/sources/visualizations/panels-visualizations/panel-overview/index.md index edb390ef143..299f3963e30 100644 --- a/docs/sources/panels-visualizations/panel-overview/index.md +++ b/docs/sources/visualizations/panels-visualizations/panel-overview/index.md @@ -102,6 +102,8 @@ refs: destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-thresholds/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-thresholds/ +aliases: + - ../../panels-visualizations/panel-overview/ # /docs/grafana/next/panels-visualizations/panel-overview/ --- # Panel overview diff --git a/docs/sources/panels-visualizations/query-transform-data/_index.md b/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md similarity index 93% rename from docs/sources/panels-visualizations/query-transform-data/_index.md rename to docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md index 10d810409c0..42dcf1338b7 100644 --- a/docs/sources/panels-visualizations/query-transform-data/_index.md +++ b/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md @@ -1,18 +1,19 @@ --- aliases: - - ../panels/expressions/ - - ../panels/inspect-panel/ - - ../panels/queries/ - - ../panels/query-a-data-source/ - - ../panels/query-a-data-source/about-queries/ - - ../panels/query-a-data-source/add-a-query/ - - ../panels/query-a-data-source/manage-queries/ - - ../panels/query-a-data-source/navigate-query-tab/ - - ../panels/query-options/ - - ../panels/reference-query-options/ - - ../panels/share-query-results/ - - manage-queries/ - - query-options/ + - ../../panels-visualizations/manage-queries/ # /docs/grafana/next/panels-visualizations/manage-queries/ + - ../../panels-visualizations/query-options/ # /docs/grafana/next/panels-visualizations/query-options/ + - ../../panels-visualizations/query-transform-data/ # /docs/grafana/next/panels-visualizations/query-transform-data/ + - ../../panels/expressions/ # /docs/grafana/next/panels/expressions/ + - ../../panels/inspect-panel/ # /docs/grafana/next/panels/inspect-panel/ + - ../../panels/queries/ # /docs/grafana/next/panels/queries/ + - ../../panels/query-a-data-source/ # /docs/grafana/next/panels/query-a-data-source/ + - ../../panels/query-a-data-source/about-queries/ # /docs/grafana/next/panels/query-a-data-source/about-queries/ + - ../../panels/query-a-data-source/add-a-query/ # /docs/grafana/next/panels/query-a-data-source/add-a-query/ + - ../../panels/query-a-data-source/manage-queries/ # /docs/grafana/next/panels/query-a-data-source/manage-queries/ + - ../../panels/query-a-data-source/navigate-query-tab/ # /docs/grafana/next/panels/query-a-data-source/navigate-query-tab/ + - ../../panels/query-options/ # /docs/grafana/next/panels/query-options/ + - ../../panels/reference-query-options/ # /docs/grafana/next/panels/reference-query-options/ + - ../../panels/share-query-results/ # /docs/grafana/next/panels/share-query-results/ labels: products: - cloud diff --git a/docs/sources/panels-visualizations/query-transform-data/calculation-types/index.md b/docs/sources/visualizations/panels-visualizations/query-transform-data/calculation-types/index.md similarity index 81% rename from docs/sources/panels-visualizations/query-transform-data/calculation-types/index.md rename to docs/sources/visualizations/panels-visualizations/query-transform-data/calculation-types/index.md index 1306197d901..6fe97955ba8 100644 --- a/docs/sources/panels-visualizations/query-transform-data/calculation-types/index.md +++ b/docs/sources/visualizations/panels-visualizations/query-transform-data/calculation-types/index.md @@ -1,9 +1,10 @@ --- aliases: - - ../../panels/calculation-types/ # /docs/grafana/<GRAFANA_VERSION>/panels/calculation-types/ - - ../../panels/calculations-list/ # /docs/grafana/<GRAFANA_VERSION>/panels/calculations-list/ - - ../../panels/reference-calculation-types/ # /docs/grafana/<GRAFANA_VERSION>/panels/reference-calculation-types/ - - ../calculation-types/ # /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/calculation-types/ + - ../../../panels-visualizations/calculation-types/ # /docs/grafana/next/panels-visualizations/calculation-types/ + - ../../../panels-visualizations/query-transform-data/calculation-types/ # /docs/grafana/next/panels-visualizations/query-transform-data/calculation-types/ + - ../../../panels/calculation-types/ # /docs/grafana/<GRAFANA_VERSION>/panels/calculation-types/ + - ../../../panels/calculations-list/ # /docs/grafana/<GRAFANA_VERSION>/panels/calculations-list/ + - ../../../panels/reference-calculation-types/ # /docs/grafana/<GRAFANA_VERSION>/panels/reference-calculation-types/ labels: products: - cloud diff --git a/docs/sources/panels-visualizations/query-transform-data/expression-queries/index.md b/docs/sources/visualizations/panels-visualizations/query-transform-data/expression-queries/index.md similarity index 94% rename from docs/sources/panels-visualizations/query-transform-data/expression-queries/index.md rename to docs/sources/visualizations/panels-visualizations/query-transform-data/expression-queries/index.md index 652f668d106..cf6432be27a 100644 --- a/docs/sources/panels-visualizations/query-transform-data/expression-queries/index.md +++ b/docs/sources/visualizations/panels-visualizations/query-transform-data/expression-queries/index.md @@ -1,9 +1,10 @@ --- aliases: - - ../../panels/query-a-data-source/use-expressions-to-manipulate-data/ - - ../../panels/query-a-data-source/use-expressions-to-manipulate-data/about-expressions/ - - ../../panels/query-a-data-source/use-expressions-to-manipulate-data/write-an-expression/ - - ./ + - ../../../panels-visualizations/query-transform-data/ # /docs/grafana/next/panels-visualizations/query-transform-data/ + - ../../../panels-visualizations/query-transform-data/expression-queries/ # /docs/grafana/next/panels-visualizations/query-transform-data/expression-queries/ + - ../../../panels/query-a-data-source/use-expressions-to-manipulate-data/ # /docs/grafana/next/panels/query-a-data-source/use-expressions-to-manipulate-data/ + - ../../../panels/query-a-data-source/use-expressions-to-manipulate-data/about-expressions/ # /docs/grafana/next/panels/query-a-data-source/use-expressions-to-manipulate-data/about-expressions/ + - ../../../panels/query-a-data-source/use-expressions-to-manipulate-data/write-an-expression/ # /docs/grafana/next/panels/query-a-data-source/use-expressions-to-manipulate-data/write-an-expression/ labels: products: - cloud diff --git a/docs/sources/panels-visualizations/query-transform-data/share-query/index.md b/docs/sources/visualizations/panels-visualizations/query-transform-data/share-query/index.md similarity index 89% rename from docs/sources/panels-visualizations/query-transform-data/share-query/index.md rename to docs/sources/visualizations/panels-visualizations/query-transform-data/share-query/index.md index a7da8d12822..0dce820cc7b 100644 --- a/docs/sources/panels-visualizations/query-transform-data/share-query/index.md +++ b/docs/sources/visualizations/panels-visualizations/query-transform-data/share-query/index.md @@ -1,6 +1,7 @@ --- aliases: - - ../../panels/query-a-data-source/share-query/ + - ../../../panels/query-a-data-source/share-query/ # /docs/grafana/next/panels/query-a-data-source/share-query/ + - ../../../panels-visualizations/query-transform-data/share-query/ # /docs/grafana/next/panels-visualizations/query-transform-data/share-query/ labels: products: - cloud diff --git a/docs/sources/panels-visualizations/query-transform-data/sql-expressions/index.md b/docs/sources/visualizations/panels-visualizations/query-transform-data/sql-expressions/index.md similarity index 100% rename from docs/sources/panels-visualizations/query-transform-data/sql-expressions/index.md rename to docs/sources/visualizations/panels-visualizations/query-transform-data/sql-expressions/index.md diff --git a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md b/docs/sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md similarity index 97% rename from docs/sources/panels-visualizations/query-transform-data/transform-data/index.md rename to docs/sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md index fda106213f9..b6b3499b4d7 100644 --- a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md +++ b/docs/sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md @@ -18,21 +18,21 @@ comments: | Browse to http://localhost:3003/docs/grafana/latest/panels-visualizations/query-transform-data/transform-data/ Refer to ./docs/README.md "Content guidelines" for more information about editing and building these docs. - aliases: - - ../../panels/reference-transformation-functions/ - - ../../panels/transform-data/ - - ../../panels/transform-data/about-transformation/ - - ../../panels/transform-data/add-transformation-to-data/ - - ../../panels/transform-data/apply-transformation-to-data/ - - ../../panels/transform-data/debug-transformation/ - - ../../panels/transform-data/delete-transformation/ - - ../../panels/transform-data/transformation-functions/ - - ../../panels/transformations/ - - ../../panels/transformations/apply-transformations/ - - ../../panels/transformations/config-from-query/ - - ../../panels/transformations/rows-to-fields/ - - ../../panels/transformations/types-options/ + - ../../../panels/transform-data/ # /docs/grafana/next/panels/transform-data/ + - ../../../panels/transform-data/about-transformation/ # /docs/grafana/next/panels/transform-data/about-transformation/ + - ../../../panels/transform-data/add-transformation-to-data/ # /docs/grafana/next/panels/transform-data/add-transformation-to-data/ + - ../../../panels/transform-data/apply-transformation-to-data/ # /docs/grafana/next/panels/transform-data/apply-transformation-to-data/ + - ../../../panels/transform-data/debug-transformation/ # /docs/grafana/next/panels/transform-data/debug-transformation/ + - ../../../panels/transform-data/delete-transformation/ # /docs/grafana/next/panels/transform-data/delete-transformation/ + - ../../../panels/transform-data/transformation-functions/ # /docs/grafana/next/panels/transform-data/transformation-functions/ + - ../../../panels/transformations/ # /docs/grafana/next/panels/transformations/ + - ../../../panels/transformations/apply-transformations/ # /docs/grafana/next/panels/transformations/apply-transformations/ + - ../../../panels/transformations/config-from-query/ # /docs/grafana/next/panels/transformations/config-from-query/ + - ../../../panels/transformations/rows-to-fields/ # /docs/grafana/next/panels/transformations/rows-to-fields/ + - ../../../panels/transformations/types-options/ # /docs/grafana/next/panels/transformations/types-options/ + - ../../../panels/reference-transformation-functions/ # /docs/grafana/next/panels/reference-transformation-functions/ + - ../../../panels-visualizations/query-transform-data/transform-data/ # /docs/grafana/next/panels-visualizations/query-transform-data/transform-data/ labels: products: - cloud diff --git a/docs/sources/panels-visualizations/query-transform-data/troubleshoot-queries/index.md b/docs/sources/visualizations/panels-visualizations/query-transform-data/troubleshoot-queries/index.md similarity index 85% rename from docs/sources/panels-visualizations/query-transform-data/troubleshoot-queries/index.md rename to docs/sources/visualizations/panels-visualizations/query-transform-data/troubleshoot-queries/index.md index 5fd433320b9..d967aa9d80e 100644 --- a/docs/sources/panels-visualizations/query-transform-data/troubleshoot-queries/index.md +++ b/docs/sources/visualizations/panels-visualizations/query-transform-data/troubleshoot-queries/index.md @@ -1,6 +1,7 @@ --- aliases: - - ../../troubleshooting/troubleshoot-queries/ + - ../../../troubleshooting/troubleshoot-queries/ # /docs/grafana/next/troubleshooting/troubleshoot-queries/ + - ../../../panels-visualizations/query-transform-data/troubleshoot-queries/ # /docs/grafana/next/panels-visualizations/query-transform-data/troubleshoot-queries/ description: Troubleshoot Grafana queries keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/_index.md b/docs/sources/visualizations/panels-visualizations/visualizations/_index.md similarity index 95% rename from docs/sources/panels-visualizations/visualizations/_index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/_index.md index 454a9884dbc..a23a49ba713 100644 --- a/docs/sources/panels-visualizations/visualizations/_index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/_index.md @@ -1,11 +1,12 @@ --- aliases: - - ../features/panels/graph/ - - ../panels/visualizations/ - - ../panels/visualizations/graph-panel/ - - ../reference/graph/ - - ../visualizations/ - - ../visualizations/graph-panel/ + - ../../features/panels/graph/ # /docs/grafana/next/features/panels/graph/ + - ../../panels-visualizations/visualizations/ # /docs/grafana/next/panels-visualizations/visualizations/ + - ../../panels/visualizations/ # /docs/grafana/next/panels/visualizations/ + - ../../panels/visualizations/graph-panel/ # /docs/grafana/next/panels/visualizations/graph-panel/ + - ../../reference/graph/ # /docs/grafana/next/reference/graph/ + - ../../visualizations/ # /docs/grafana/next/visualizations/ + - ../../visualizations/graph-panel/ # /docs/grafana/next/visualizations/graph-panel/ labels: products: - cloud diff --git a/docs/sources/panels-visualizations/visualizations/alert-list/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/alert-list/index.md similarity index 91% rename from docs/sources/panels-visualizations/visualizations/alert-list/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/alert-list/index.md index 3e6550a8f5e..31ad53d0251 100644 --- a/docs/sources/panels-visualizations/visualizations/alert-list/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/alert-list/index.md @@ -1,9 +1,10 @@ --- aliases: - - ../../features/panels/alertlist/ - - ../../panels/visualizations/alert-list-panel/ - - ../../reference/alertlist/ - - ../../visualizations/alert-list-panel/ + - ../../../features/panels/alertlist/ # /docs/grafana/next/features/panels/alertlist/ + - ../../../panels-visualizations/visualizations/alert-list/ # /docs/grafana/next/panels-visualizations/visualizations/alert-list/ + - ../../../panels/visualizations/alert-list-panel/ # /docs/grafana/next/panels/visualizations/alert-list-panel/ + - ../../../reference/alertlist/ # /docs/grafana/next/reference/alertlist/ + - ../../../visualizations/alert-list-panel/ # /docs/grafana/next/visualizations/alert-list-panel/ keywords: - grafana - alert list diff --git a/docs/sources/panels-visualizations/visualizations/annotations/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/annotations/index.md similarity index 90% rename from docs/sources/panels-visualizations/visualizations/annotations/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/annotations/index.md index d69ea3d95de..ff53ee4e0f4 100644 --- a/docs/sources/panels-visualizations/visualizations/annotations/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/annotations/index.md @@ -1,8 +1,9 @@ --- aliases: - - ../../features/panels/annotations/ - - ../../panels/visualizations/annotations/ - - ../../visualizations/annotations/ + - ../../../features/panels/annotations/ # /docs/grafana/next/features/panels/annotations/ + - ../../../panels/visualizations/annotations/ # /docs/grafana/next/panels/visualizations/annotations/ + - ../../annotations/ # /docs/grafana/next/visualizations/annotations/ + - ../../../panels-visualizations/visualizations/annotations/ # /docs/grafana/next/panels-visualizations/visualizations/annotations/ description: Configure options for Grafana's annotations list visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/bar-chart/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/bar-chart/index.md similarity index 97% rename from docs/sources/panels-visualizations/visualizations/bar-chart/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/bar-chart/index.md index 45a847aeac3..9e3f445e612 100644 --- a/docs/sources/panels-visualizations/visualizations/bar-chart/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/bar-chart/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../panels/visualizations/bar-chart/ - - ../../visualizations/bar-chart/ + - ../../../panels/visualizations/bar-chart/ # /docs/grafana/next/panels/visualizations/bar-chart/ + - ../../bar-chart/ # /docs/grafana/next/visualizations/bar-chart/ + - ../../../panels-visualizations/visualizations/bar-chart/ # /docs/grafana/next/panels-visualizations/visualizations/bar-chart/ description: Configure options for Grafana's bar chart visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/bar-gauge/index.md similarity index 95% rename from docs/sources/panels-visualizations/visualizations/bar-gauge/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/bar-gauge/index.md index 72862007d31..90be816c461 100644 --- a/docs/sources/panels-visualizations/visualizations/bar-gauge/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/bar-gauge/index.md @@ -1,8 +1,9 @@ --- aliases: - - ../../features/panels/bar_gauge/ - - ../../panels/visualizations/bar-gauge-panel/ - - ../../visualizations/bar-gauge-panel/ + - ../../../features/panels/bar_gauge/ # /docs/grafana/next/features/panels/bar_gauge/ + - ../../../panels/visualizations/bar-gauge-panel/ # /docs/grafana/next/panels/visualizations/bar-gauge-panel/ + - ../../bar-gauge-panel/ # /docs/grafana/next/visualizations/bar-gauge-panel/ + - ../../../panels-visualizations/visualizations/bar-gauge/ # /docs/grafana/next/panels-visualizations/visualizations/bar-gauge/ description: Configure options for Grafana's bar gauge visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/candlestick/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/candlestick/index.md similarity index 98% rename from docs/sources/panels-visualizations/visualizations/candlestick/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/candlestick/index.md index dc0df1d1e9f..bc07d805000 100644 --- a/docs/sources/panels-visualizations/visualizations/candlestick/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/candlestick/index.md @@ -1,8 +1,9 @@ --- aliases: - - ../../features/panels/candlestick/ - - ../../panels/visualizations/candlestick/ - - ../../visualizations/candlestick/ + - ../../../features/panels/candlestick/ # /docs/grafana/next/features/panels/candlestick/ + - ../../../panels/visualizations/candlestick/ # /docs/grafana/next/panels/visualizations/candlestick/ + - ../../candlestick/ # /docs/grafana/next/visualizations/candlestick/ + - ../../../panels-visualizations/visualizations/candlestick/ # /docs/grafana/next/panels-visualizations/visualizations/candlestick/ description: Configure options for Grafana's candlestick visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/canvas/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/canvas/index.md similarity index 99% rename from docs/sources/panels-visualizations/visualizations/canvas/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/canvas/index.md index 76a18031449..821d547d7d3 100644 --- a/docs/sources/panels-visualizations/visualizations/canvas/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/canvas/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../features/panels/canvas/ - - ../../visualizations/canvas/ + - ../../../features/panels/canvas/ # /docs/grafana/next/features/panels/canvas/ + - ../../canvas/ # /docs/grafana/next/visualizations/canvas/ + - ../../../panels-visualizations/visualizations/canvas/ # /docs/grafana/next/panels-visualizations/visualizations/canvas/ description: Configure options for Grafana's canvas visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/dashboard-list/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/dashboard-list/index.md similarity index 90% rename from docs/sources/panels-visualizations/visualizations/dashboard-list/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/dashboard-list/index.md index 92e12996630..5c6b4aa8d4a 100644 --- a/docs/sources/panels-visualizations/visualizations/dashboard-list/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/dashboard-list/index.md @@ -1,9 +1,10 @@ --- aliases: - - ../../features/panels/dashlist/ - - ../../panels/visualizations/dashboard-list-panel/ - - ../../reference/dashlist/ - - ../../visualizations/dashboard-list-panel/ + - ../../../features/panels/dashlist/ # /docs/grafana/next/features/panels/dashlist/ + - ../../../panels/visualizations/dashboard-list-panel/ # /docs/grafana/next/panels/visualizations/dashboard-list-panel/ + - ../../../reference/dashlist/ # /docs/grafana/next/reference/dashlist/ + - ../../dashboard-list-panel/ # /docs/grafana/next/visualizations/dashboard-list-panel/ + - ../../../panels-visualizations/visualizations/dashboard-list/ # /docs/grafana/next/panels-visualizations/visualizations/dashboard-list/ keywords: - grafana - dashboard list diff --git a/docs/sources/panels-visualizations/visualizations/datagrid/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/datagrid/index.md similarity index 94% rename from docs/sources/panels-visualizations/visualizations/datagrid/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/datagrid/index.md index 60d9aab5201..b54326968a6 100644 --- a/docs/sources/panels-visualizations/visualizations/datagrid/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/datagrid/index.md @@ -1,8 +1,9 @@ --- aliases: - - ../../features/panels/datagrid_panel/ - - ../../reference/datagrid/ - - ../../visualizations/datagrid/ + - ../../../features/panels/datagrid_panel/ # /docs/grafana/next/features/panels/datagrid_panel/ + - ../../../reference/datagrid/ # /docs/grafana/next/reference/datagrid/ + - ../../datagrid/ # /docs/grafana/next/visualizations/datagrid/ + - ../../../panels-visualizations/visualizations/datagrid/ # /docs/grafana/next/panels-visualizations/visualizations/datagrid/ description: Configure options for Grafana's datagrid visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/flame-graph/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/flame-graph/index.md similarity index 98% rename from docs/sources/panels-visualizations/visualizations/flame-graph/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/flame-graph/index.md index 362acb1384a..be79dcbb317 100644 --- a/docs/sources/panels-visualizations/visualizations/flame-graph/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/flame-graph/index.md @@ -1,6 +1,7 @@ --- aliases: - - ../../visualizations/flame-graph/ + - ../../flame-graph/ # /docs/grafana/next/visualizations/flame-graph/ + - ../../../panels-visualizations/visualizations/flame-graph/ # /docs/grafana/next/panels-visualizations/visualizations/flame-graph/ keywords: - grafana - dashboard diff --git a/docs/sources/panels-visualizations/visualizations/gauge/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/gauge/index.md similarity index 96% rename from docs/sources/panels-visualizations/visualizations/gauge/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/gauge/index.md index fbb1cb922ae..496abc455f4 100644 --- a/docs/sources/panels-visualizations/visualizations/gauge/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/gauge/index.md @@ -1,8 +1,9 @@ --- aliases: - - ../../features/panels/gauge/ - - ../../panels/visualizations/gauge-panel/ - - ../../visualizations/gauge-panel/ + - ../../../features/panels/gauge/ # /docs/grafana/next/features/panels/gauge/ + - ../../../panels/visualizations/gauge-panel/ # /docs/grafana/next/panels/visualizations/gauge-panel/ + - ../../gauge-panel/ # /docs/grafana/next/visualizations/gauge-panel/ + - ../../../panels-visualizations/visualizations/gauge/ # /docs/grafana/next/panels-visualizations/visualizations/gauge/ description: Configure options for Grafana's gauge visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/geomap/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/geomap/index.md similarity index 94% rename from docs/sources/panels-visualizations/visualizations/geomap/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/geomap/index.md index 030b002760b..241622708db 100644 --- a/docs/sources/panels-visualizations/visualizations/geomap/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/geomap/index.md @@ -1,26 +1,27 @@ --- aliases: - - ../../features/panels/geomap/ - - ../../features/panels/geomap/arcgis/ - - ../../features/panels/geomap/carto/ - - ../../features/panels/geomap/controls/ - - ../../features/panels/geomap/daynight/ - - ../../features/panels/geomap/geojson/ - - ../../features/panels/geomap/heatmap/ - - ../../features/panels/geomap/markers/ - - ../../features/panels/geomap/osm/ - - ../../features/panels/geomap/zyx/ - - ../../panels/visualizations/geomap/ - - ../../panels/visualizations/geomap/arcgis/ - - ../../panels/visualizations/geomap/carto/ - - ../../panels/visualizations/geomap/controls/ - - ../../panels/visualizations/geomap/daynight/ - - ../../panels/visualizations/geomap/geojson/ - - ../../panels/visualizations/geomap/heatmap/ - - ../../panels/visualizations/geomap/markers/ - - ../../panels/visualizations/geomap/osm/ - - ../../panels/visualizations/geomap/zyx/ - - ../../visualizations/geomap/ + - ../../../features/panels/geomap/ # /docs/grafana/next/features/panels/geomap/ + - ../../../features/panels/geomap/arcgis/ # /docs/grafana/next/features/panels/geomap/arcgis/ + - ../../../features/panels/geomap/carto/ # /docs/grafana/next/features/panels/geomap/carto/ + - ../../../features/panels/geomap/controls/ # /docs/grafana/next/features/panels/geomap/controls/ + - ../../../features/panels/geomap/daynight/ # /docs/grafana/next/features/panels/geomap/daynight/ + - ../../../features/panels/geomap/geojson/ # /docs/grafana/next/features/panels/geomap/geojson/ + - ../../../features/panels/geomap/heatmap/ # /docs/grafana/next/features/panels/geomap/heatmap/ + - ../../../features/panels/geomap/markers/ # /docs/grafana/next/features/panels/geomap/markers/ + - ../../../features/panels/geomap/osm/ # /docs/grafana/next/features/panels/geomap/osm/ + - ../../../features/panels/geomap/zyx/ # /docs/grafana/next/features/panels/geomap/zyx/ + - ../../../panels/visualizations/geomap/ # /docs/grafana/next/panels/visualizations/geomap/ + - ../../../panels/visualizations/geomap/arcgis/ # /docs/grafana/next/panels/visualizations/geomap/arcgis/ + - ../../../panels/visualizations/geomap/carto/ # /docs/grafana/next/panels/visualizations/geomap/carto/ + - ../../../panels/visualizations/geomap/controls/ # /docs/grafana/next/panels/visualizations/geomap/controls/ + - ../../../panels/visualizations/geomap/daynight/ # /docs/grafana/next/panels/visualizations/geomap/daynight/ + - ../../../panels/visualizations/geomap/geojson/ # /docs/grafana/next/panels/visualizations/geomap/geojson/ + - ../../../panels/visualizations/geomap/heatmap/ # /docs/grafana/next/panels/visualizations/geomap/heatmap/ + - ../../../panels/visualizations/geomap/markers/ # /docs/grafana/next/panels/visualizations/geomap/markers/ + - ../../../panels/visualizations/geomap/osm/ # /docs/grafana/next/panels/visualizations/geomap/osm/ + - ../../../panels/visualizations/geomap/zyx/ # /docs/grafana/next/panels/visualizations/geomap/zyx/ + - ../../../visualizations/geomap/ # /docs/grafana/visualizations/geomap/ + - ../../../panels-visualizations/visualizations/geomap/ # /docs/grafana/next/panels-visualizations/visualizations/geomap/ description: Configure options for Grafana's geomap visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/heatmap/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/heatmap/index.md similarity index 97% rename from docs/sources/panels-visualizations/visualizations/heatmap/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/heatmap/index.md index a51479e4e7a..31ffb8295dc 100644 --- a/docs/sources/panels-visualizations/visualizations/heatmap/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/heatmap/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../features/panels/heatmap/ - - ../../visualizations/heatmap/ + - ../../../features/panels/heatmap/ # /docs/grafana/next/features/panels/heatmap/ + - ../../heatmap/ # /docs/grafana/next/visualizations/heatmap/ + - ../../../panels-visualizations/visualizations/heatmap/ # /docs/grafana/next/panels-visualizations/visualizations/heatmap/ description: Configure options for Grafana's heatmap visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/histogram/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/histogram/index.md similarity index 94% rename from docs/sources/panels-visualizations/visualizations/histogram/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/histogram/index.md index c96747809dc..c1df19215f7 100644 --- a/docs/sources/panels-visualizations/visualizations/histogram/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/histogram/index.md @@ -1,8 +1,9 @@ --- aliases: - - ../../features/panels/histogram/ - - ../../panels/visualizations/histogram/ - - ../../visualizations/histogram/ + - ../../../features/panels/histogram/ # /docs/grafana/next/features/panels/histogram/ + - ../../../panels/visualizations/histogram/ # /docs/grafana/next/panels/visualizations/histogram/ + - ../../../visualizations/histogram/ # /docs/grafana/next/visualizations/histogram/ + - ../../../panels-visualizations/visualizations/histogram/ # /docs/grafana/next/panels-visualizations/visualizations/histogram/ description: Configure options for Grafana's histogram visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/logs/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/logs/index.md similarity index 92% rename from docs/sources/panels-visualizations/visualizations/logs/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/logs/index.md index 00f0163f55f..91f179e9979 100644 --- a/docs/sources/panels-visualizations/visualizations/logs/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/logs/index.md @@ -1,9 +1,10 @@ --- aliases: - - ../../features/panels/logs/ - - ../../panels/visualizations/logs-panel/ - - ../../reference/logs/ - - ../../visualizations/logs-panel/ + - ../../../features/panels/logs/ # /docs/grafana/next/features/panels/logs/ + - ../../../panels/visualizations/logs-panel/ # /docs/grafana/next/panels/visualizations/logs-panel/ + - ../../../reference/logs/ # /docs/grafana/next/reference/logs/ + - ../../logs-panel/ # /docs/grafana/next/visualizations/logs-panel/ + - ../../../panels-visualizations/visualizations/logs/ # /docs/grafana/next/panels-visualizations/visualizations/logs/ keywords: - grafana - dashboard diff --git a/docs/sources/panels-visualizations/visualizations/news/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/news/index.md similarity index 90% rename from docs/sources/panels-visualizations/visualizations/news/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/news/index.md index 1797116334f..134a90b245f 100644 --- a/docs/sources/panels-visualizations/visualizations/news/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/news/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../panels/visualizations/news-graph/ - - ../../visualizations/news-panel/ + - ../../../panels/visualizations/news-graph/ # /docs/grafana/next/panels/visualizations/news-graph/ + - ../../news-panel/ # /docs/grafana/next/visualizations/news-panel/ + - ../../../panels-visualizations/visualizations/news/ # /docs/grafana/next/panels-visualizations/visualizations/news/ keywords: - grafana - news diff --git a/docs/sources/panels-visualizations/visualizations/node-graph/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/node-graph/index.md similarity index 98% rename from docs/sources/panels-visualizations/visualizations/node-graph/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/node-graph/index.md index a18b7349310..ba0eeb73233 100644 --- a/docs/sources/panels-visualizations/visualizations/node-graph/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/node-graph/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../panels/visualizations/node-graph/ - - ../../visualizations/node-graph/ + - ../../../panels-visualizations/visualizations/node-graph/ # /docs/grafana/next/panels-visualizations/visualizations/node-graph/ + - ../../../panels/visualizations/node-graph/ # /docs/grafana/next/panels/visualizations/node-graph/ + - ../../../visualizations/node-graph/ # /docs/grafana/next/visualizations/node-graph/ keywords: - grafana - dashboard diff --git a/docs/sources/panels-visualizations/visualizations/pie-chart/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/pie-chart/index.md similarity index 96% rename from docs/sources/panels-visualizations/visualizations/pie-chart/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/pie-chart/index.md index 0c46b734d1d..c29e80febdb 100644 --- a/docs/sources/panels-visualizations/visualizations/pie-chart/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/pie-chart/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../panels/visualizations/pie-chart-pane/ - - ../../visualizations/pie-chart-panel/ + - ../../../panels/visualizations/pie-chart-pane/ # /docs/grafana/next/panels/visualizations/pie-chart-pane/ + - ../../pie-chart-panel/ # /docs/grafana/next/visualizations/pie-chart-panel/ + - ../../../panels-visualizations/visualizations/pie-chart/ # /docs/grafana/next/panels-visualizations/visualizations/pie-chart/ keywords: - grafana - pie chart diff --git a/docs/sources/panels-visualizations/visualizations/stat/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/stat/index.md similarity index 95% rename from docs/sources/panels-visualizations/visualizations/stat/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/stat/index.md index e1ad7fada4a..383322d6d27 100644 --- a/docs/sources/panels-visualizations/visualizations/stat/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/stat/index.md @@ -1,10 +1,11 @@ --- aliases: - - ../../features/panels/singlestat/ - - ../../features/panels/stat/ - - ../../panels/visualizations/stat-panel/ - - ../../reference/singlestat/ - - ../../visualizations/stat-panel/ + - ../../../features/panels/singlestat/ # /docs/grafana/next/features/panels/singlestat/ + - ../../../features/panels/stat/ # /docs/grafana/next/features/panels/stat/ + - ../../../panels/visualizations/stat-panel/ # /docs/grafana/next/panels/visualizations/stat-panel/ + - ../../../reference/singlestat/ # /docs/grafana/next/reference/singlestat/ + - ../../stat-panel/ # /docs/grafana/next/visualizations/stat-panel/ + - ../../../panels-visualizations/visualizations/stat/ # /docs/grafana/next/panels-visualizations/visualizations/stat/ description: Configure options for Grafana's stat visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/state-timeline/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/state-timeline/index.md similarity index 96% rename from docs/sources/panels-visualizations/visualizations/state-timeline/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/state-timeline/index.md index 2bf81512885..c5840be810d 100644 --- a/docs/sources/panels-visualizations/visualizations/state-timeline/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/state-timeline/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../panels/visualizations/state-timeline/ - - ../../visualizations/state-timeline/ + - ../../../panels/visualizations/state-timeline/ # /docs/grafana/next/panels/visualizations/state-timeline/ + - ../../state-timeline/ # /docs/grafana/next/visualizations/state-timeline/ + - ../../../panels-visualizations/visualizations/state-timeline/ # /docs/grafana/next/panels-visualizations/visualizations/state-timeline/ description: Configure options for Grafana's state timeline visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/status-history/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/status-history/index.md similarity index 95% rename from docs/sources/panels-visualizations/visualizations/status-history/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/status-history/index.md index 6c7fdb4fd5e..4b30df76171 100644 --- a/docs/sources/panels-visualizations/visualizations/status-history/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/status-history/index.md @@ -1,7 +1,8 @@ --- aliases: - - ../../panels/visualizations/status-history/ - - ../../visualizations/status-history/ + - ../../../panels/visualizations/status-history/ # /docs/grafana/next/panels/visualizations/status-history/ + - ../../status-history/ # /docs/grafana/next/visualizations/status-history/ + - ../../../panels-visualizations/visualizations/status-history/ # /docs/grafana/next/panels-visualizations/visualizations/status-history/ description: Configure options for Grafana's status history visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/table/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/table/index.md similarity index 97% rename from docs/sources/panels-visualizations/visualizations/table/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/table/index.md index 24185ffc378..84015fa7357 100644 --- a/docs/sources/panels-visualizations/visualizations/table/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/table/index.md @@ -1,11 +1,12 @@ --- aliases: - - ../../features/panels/table_panel/ - - ../../panels/visualizations/table/filter-table-columns/ - - ../../reference/table/ - - ../../visualizations/table/ - - ../../visualizations/table/filter-table-columns/ - - /docs/grafana/next/panels/visualizations/table/table-field-options/ + - ../../../features/panels/table_panel/ # /docs/grafana/next/features/panels/table_panel/ + - ../../../panels/visualizations/table/filter-table-columns/ # /docs/grafana/next/panels/visualizations/table/filter-table-columns/ + - ../../../reference/table/ # /docs/grafana/next/reference/table/ + - ../../table/ # /docs/grafana/next/visualizations/table/ + - ../../table/filter-table-columns/ # /docs/grafana/next/visualizations/table/filter-table-columns/ + - ../../../panels/visualizations/table/table-field-options/ # /docs/grafana/next/panels/visualizations/table/table-field-options/ + - ../../../panels-visualizations/visualizations/table/ # /docs/grafana/next/panels-visualizations/visualizations/table/ description: Configure options for Grafana's table visualization keywords: - grafana diff --git a/docs/sources/panels-visualizations/visualizations/text/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/text/index.md similarity index 87% rename from docs/sources/panels-visualizations/visualizations/text/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/text/index.md index fefcc32d251..5647fcff04b 100644 --- a/docs/sources/panels-visualizations/visualizations/text/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/text/index.md @@ -1,9 +1,10 @@ --- aliases: - - ../../features/panels/text/ - - ../../panels/visualizations/text-panel/ - - ../../reference/alertlist/ - - ../../visualizations/text-panel/ + - ../../../features/panels/text/ # /docs/grafana/next/features/panels/text/ + - ../../../panels-visualizations/visualizations/text/ # /docs/grafana/next/panels-visualizations/visualizations/text/ + - ../../../panels/visualizations/text-panel/ # /docs/grafana/next/panels/visualizations/text-panel/ + - ../../../reference/alertlist/ # /docs/grafana/next/reference/alertlist/ + - ../../../visualizations/text-panel/ # /docs/grafana/next/visualizations/text-panel/ keywords: - grafana - text diff --git a/docs/sources/panels-visualizations/visualizations/time-series/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/time-series/index.md similarity index 79% rename from docs/sources/panels-visualizations/visualizations/time-series/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/time-series/index.md index ad1b84f8a27..e7c51fb74cf 100644 --- a/docs/sources/panels-visualizations/visualizations/time-series/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/time-series/index.md @@ -1,22 +1,23 @@ --- aliases: - - ../../features/panels/histogram/ - - ../../panels/visualizations/time-series/ - - ../../panels/visualizations/time-series/annotate-time-series/ - - ../../panels/visualizations/time-series/change-axis-display/ - - ../../panels/visualizations/time-series/graph-color-scheme/ - - ../../panels/visualizations/time-series/graph-time-series-as-bars/ - - ../../panels/visualizations/time-series/graph-time-series-as-lines/ - - ../../panels/visualizations/time-series/graph-time-series-as-points/ - - ../../panels/visualizations/time-series/graph-time-series-stacking/ - - ../../visualizations/time-series/ - - ../../visualizations/time-series/annotate-time-series/ - - ../../visualizations/time-series/change-axis-display/ - - ../../visualizations/time-series/graph-color-scheme/ - - ../../visualizations/time-series/graph-time-series-as-bars/ - - ../../visualizations/time-series/graph-time-series-as-lines/ - - ../../visualizations/time-series/graph-time-series-as-points/ - - ../../visualizations/time-series/graph-time-series-stacking/ + - ../../../features/panels/histogram/ # /docs/grafana/next/features/panels/histogram/ + - ../../../panels/visualizations/time-series/ # /docs/grafana/next/panels/visualizations/time-series/ + - ../../../panels/visualizations/time-series/annotate-time-series/ # /docs/grafana/next/panels/visualizations/time-series/annotate-time-series/ + - ../../../panels/visualizations/time-series/change-axis-display/ # /docs/grafana/next/panels/visualizations/time-series/change-axis-display/ + - ../../../panels/visualizations/time-series/graph-color-scheme/ # /docs/grafana/next/panels/visualizations/time-series/graph-color-scheme/ + - ../../../panels/visualizations/time-series/graph-time-series-as-bars/ # /docs/grafana/next/panels/visualizations/time-series/graph-time-series-as-bars/ + - ../../../panels/visualizations/time-series/graph-time-series-as-lines/ # /docs/grafana/next/panels/visualizations/time-series/graph-time-series-as-lines/ + - ../../../panels/visualizations/time-series/graph-time-series-as-points/ # /docs/grafana/next/panels/visualizations/time-series/graph-time-series-as-points/ + - ../../../panels/visualizations/time-series/graph-time-series-stacking/ # /docs/grafana/next/panels/visualizations/time-series/graph-time-series-stacking/ + - ../../time-series/ # /docs/grafana/next/visualizations/time-series/ + - ../../time-series/annotate-time-series/ # /docs/grafana/next/visualizations/time-series/annotate-time-series/ + - ../../time-series/change-axis-display/ # /docs/grafana/next/visualizations/time-series/change-axis-display/ + - ../../time-series/graph-color-scheme/ # /docs/grafana/next/visualizations/time-series/graph-color-scheme/ + - ../../time-series/graph-time-series-as-bars/ # /docs/grafana/next/visualizations/time-series/graph-time-series-as-bars/ + - ../../time-series/graph-time-series-as-lines/ # /docs/grafana/next/visualizations/time-series/graph-time-series-as-lines/ + - ../../time-series/graph-time-series-as-points/ # /docs/grafana/next/visualizations/time-series/graph-time-series-as-points/ + - ../../time-series/graph-time-series-stacking/ # /docs/grafana/next/visualizations/time-series/graph-time-series-stacking/ + - ../../../panels-visualizations/visualizations/time-series/ # /docs/grafana/next/panels-visualizations/visualizations/time-series/ keywords: - grafana - graph panel diff --git a/docs/sources/panels-visualizations/visualizations/traces/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/traces/index.md similarity index 98% rename from docs/sources/panels-visualizations/visualizations/traces/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/traces/index.md index abf7fc1ddc3..f9e2a74d16d 100644 --- a/docs/sources/panels-visualizations/visualizations/traces/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/traces/index.md @@ -1,6 +1,7 @@ --- aliases: - - ../../visualizations/traces/ + - ../../traces/ # /docs/grafana/next/visualizations/traces/ + - ../../../panels-visualizations/visualizations/traces/ # /docs/grafana/next/panels-visualizations/visualizations/traces/ keywords: - grafana - dashboard diff --git a/docs/sources/panels-visualizations/visualizations/trend/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/trend/index.md similarity index 96% rename from docs/sources/panels-visualizations/visualizations/trend/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/trend/index.md index 21b58fd1b0f..c685bc15d48 100644 --- a/docs/sources/panels-visualizations/visualizations/trend/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/trend/index.md @@ -21,6 +21,8 @@ refs: destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/visualizations/time-series/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/visualizations/panels-visualizations/visualizations/time-series/ +aliases: + - ../../../panels-visualizations/visualizations/trend/ # /docs/grafana/next/panels-visualizations/visualizations/trend/ --- # Trend diff --git a/docs/sources/panels-visualizations/visualizations/xy-chart/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/xy-chart/index.md similarity index 98% rename from docs/sources/panels-visualizations/visualizations/xy-chart/index.md rename to docs/sources/visualizations/panels-visualizations/visualizations/xy-chart/index.md index 72d59e961cc..c8eab88c838 100644 --- a/docs/sources/panels-visualizations/visualizations/xy-chart/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/xy-chart/index.md @@ -31,6 +31,8 @@ refs: destination: /docs/grafana/<GRAFANA_VERSION>/panels-visualizations/configure-overrides/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana-cloud/visualizations/panels-visualizations/configure-overrides/ +aliases: + - ../../../panels-visualizations/visualizations/xy-chart/ # /docs/grafana/next/panels-visualizations/visualizations/xy-chart/ --- # XY chart diff --git a/docs/sources/explore/simplified-exploration/_index.md b/docs/sources/visualizations/simplified-exploration/_index.md similarity index 92% rename from docs/sources/explore/simplified-exploration/_index.md rename to docs/sources/visualizations/simplified-exploration/_index.md index 360b37aee5a..f812dd1d4c5 100644 --- a/docs/sources/explore/simplified-exploration/_index.md +++ b/docs/sources/visualizations/simplified-exploration/_index.md @@ -4,8 +4,9 @@ keywords: - Simplified exploration - queryless - Explore apps -title: Simplified exploration -menuTitle: Simplified exploration + - Drilldown apps +title: Drilldown +menuTitle: Drilldown weight: 100 hero: title: Simplified exploration with the Drilldown apps @@ -32,9 +33,11 @@ cards: href: ./profiles/ description: View and analyze high-level service performance, identify problem processes for optimization, and diagnose issues to determine root causes. height: 24 +aliases: + - ../explore/simplified-exploration/ # /docs/grafana/next/explore/simplified-exploration/ --- -# Simplified exploration +# Drilldown The Grafana Drilldown apps are designed for effortless data exploration through intuitive, queryless interactions. From 4a202ed2c46e62d64ad72940eebdc91044c2b5ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:48:33 -0400 Subject: [PATCH 47/75] Bump alpine from 3.21.3 to 3.22.2 (#112191) Bumps alpine from 3.21.3 to 3.22.2. --- updated-dependencies: - dependency-name: alpine dependency-version: 3.22.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 77c825591f4..3fdc7f8c40c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ ARG JS_SRC=js-builder # Dependabot cannot update dependencies listed in ARGs # By using FROM instructions we can delegate dependency updates to dependabot -FROM alpine:3.21.3 AS alpine-base +FROM alpine:3.22.2 AS alpine-base FROM ubuntu:22.04 AS ubuntu-base FROM golang:1.25.3-alpine AS go-builder-base FROM --platform=${JS_PLATFORM} node:22-alpine AS js-builder-base From 7c4b5f728eb28ec909e146cea18dc04fda496a4f Mon Sep 17 00:00:00 2001 From: Dave Henderson <dave.henderson@grafana.com> Date: Wed, 15 Oct 2025 14:25:59 -0400 Subject: [PATCH 48/75] fix(dependabot): Add missing go module directories (#112469) Signed-off-by: Dave Henderson <dave.henderson@grafana.com> --- .github/dependabot.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0f7096c314b..79987600b9a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,20 +11,35 @@ updates: prefix: deps(go) directories: - "/" - - "/apps/playlist" - - "/apps/secret" + - "/apps/advisor" + - "/apps/alerting/alertenrichment" + - "/apps/alerting/notifications" + - "/apps/alerting/rules" + - "/apps/correlations" + - "/apps/dashboard" + - "/apps/folder" + - "/apps/iam" - "/apps/investigations" + - "/apps/playlist" + - "/apps/plugins" + - "/apps/preferences" + - "/apps/provisioning" + - "/apps/scope" + - "/apps/secret" + - "/apps/shorturl" + - "/hack" - "/pkg/aggregator" - "/pkg/apimachinery" - - "/pkg/apis/folder" - "/pkg/apiserver" - "/pkg/build" - "/pkg/build/wire" + - "/pkg/codegen" + - "/pkg/plugins/codegen" - "/pkg/promlib" - "/pkg/semconv" - - "/pkg/storage/unified/apistore" - - "/pkg/storage/unified/resource" - "/pkg/util/xorm" + - "/scripts/go-workspace" + - "/scripts/modowners" schedule: interval: "daily" time: "02:00" From 9fc9914bc2661851c3d00140064daa1a60bcc2e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:37:12 +0000 Subject: [PATCH 49/75] Bump dagger/dagger-for-github from 8.0.0 to 8.2.0 (#111457) Bumps [dagger/dagger-for-github](https://github.com/dagger/dagger-for-github) from 8.0.0 to 8.2.0. - [Release notes](https://github.com/dagger/dagger-for-github/releases) - [Commits](https://github.com/dagger/dagger-for-github/compare/e47aba410ef9bb9ed81a4d2a97df31061e5e842e...d913e70051faf3b907d4dd96ef1161083c88c644) --- updated-dependencies: - dependency-name: dagger/dagger-for-github dependency-version: 8.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pr-e2e-tests.yml | 12 ++++++------ .github/workflows/release-pr.yml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index 6ade5a9c787..63dd2952534 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -65,7 +65,7 @@ jobs: # If no cache hit, build Grafana - name: Build Grafana if: steps.cache.outputs.cache-hit != 'true' - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + uses: dagger/dagger-for-github@d913e70051faf3b907d4dd96ef1161083c88c644 with: version: 0.18.8 verb: run @@ -230,7 +230,7 @@ jobs: - name: chmod +x run: chmod +x ./e2e-runner - name: Run E2E tests - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + uses: dagger/dagger-for-github@d913e70051faf3b907d4dd96ef1161083c88c644 with: version: 0.18.8 verb: run @@ -302,7 +302,7 @@ jobs: with: name: grafana-tar-gz - name: Run E2E tests - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + uses: dagger/dagger-for-github@d913e70051faf3b907d4dd96ef1161083c88c644 with: version: 0.18.8 verb: run @@ -365,7 +365,7 @@ jobs: name: grafana-tar-gz - name: Run E2E tests - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + uses: dagger/dagger-for-github@d913e70051faf3b907d4dd96ef1161083c88c644 with: version: 0.18.8 verb: run @@ -484,14 +484,14 @@ jobs: name: grafana-tar-gz - name: Run PR a11y test if: github.event_name == 'pull_request' - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + uses: dagger/dagger-for-github@d913e70051faf3b907d4dd96ef1161083c88c644 with: version: 0.18.8 verb: run args: go run ./pkg/build/a11y --package=grafana.tar.gz - name: Run non-PR a11y test if: github.event_name != 'pull_request' - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + uses: dagger/dagger-for-github@d913e70051faf3b907d4dd96ef1161083c88c644 with: version: 0.18.8 verb: run diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 2190e2640b4..e933f7e814d 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -196,7 +196,7 @@ jobs: run: git add CHANGELOG.md && git commit --allow-empty -m "Update changelog" CHANGELOG.md - name: Bump versions if: ${{ inputs.bump == true || inputs.bump == 'true' }} - uses: dagger/dagger-for-github@e47aba410ef9bb9ed81a4d2a97df31061e5e842e + uses: dagger/dagger-for-github@d913e70051faf3b907d4dd96ef1161083c88c644 with: version: 0.18.8 verb: run From fa5a3cfdf6a1c69a5fdf9e2076a7c96902218137 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:38:49 +0000 Subject: [PATCH 50/75] Bump actions/github-script from 7 to 8 (#111716) Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../workflows/detect-breaking-changes-levitate.yml | 12 ++++++------ .../workflows/detect-plugin-extension-changes.yml | 2 +- .github/workflows/pr-patch-check.yml | 2 +- .github/workflows/release-build.yml | 2 +- .github/workflows/sync-mirror-event.yml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index 82351d45785..ce2f195c026 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -230,7 +230,7 @@ jobs: name: levitate - name: Parsing levitate result - uses: actions/github-script@v7 + uses: actions/github-script@v8 id: levitate-run with: script: | @@ -241,7 +241,7 @@ jobs: # Check if label exists - name: Check if "levitate breaking change" label exists id: does-label-exist - uses: actions/github-script@v7 + uses: actions/github-script@v8 env: PR_NUMBER: ${{ github.event.pull_request.number }} with: @@ -334,7 +334,7 @@ jobs: # Add the label - name: Add "levitate breaking change" label if: steps.levitate-run.outputs.exit_code == 1 && steps.does-label-exist.outputs.result == 0 - uses: actions/github-script@v7 + uses: actions/github-script@v8 env: PR_NUMBER: ${{ steps.levitate-run.outputs.pr_number }} with: @@ -350,7 +350,7 @@ jobs: # Remove label (no more breaking changes) - name: Remove "levitate breaking change" label if: steps.levitate-run.outputs.exit_code == 0 && steps.does-label-exist.outputs.result == 1 - uses: actions/github-script@v7 + uses: actions/github-script@v8 env: PR_NUMBER: ${{ steps.levitate-run.outputs.pr_number }} with: @@ -368,7 +368,7 @@ jobs: # Related issue: https://github.com/renovatebot/renovate/issues/1908 - name: Add "grafana/plugins-platform-frontend" as a reviewer if: steps.levitate-run.outputs.exit_code == 1 - uses: actions/github-script@v7 + uses: actions/github-script@v8 env: PR_NUMBER: ${{ steps.levitate-run.outputs.pr_number }} with: @@ -385,7 +385,7 @@ jobs: # Remove reviewers (no more breaking changes) - name: Remove "grafana/plugins-platform-frontend" from the list of reviewers if: steps.levitate-run.outputs.exit_code == 0 - uses: actions/github-script@v7 + uses: actions/github-script@v8 env: PR_NUMBER: ${{ steps.levitate-run.outputs.pr_number }} with: diff --git a/.github/workflows/detect-plugin-extension-changes.yml b/.github/workflows/detect-plugin-extension-changes.yml index 12e21b159f1..e59a7c01a7c 100644 --- a/.github/workflows/detect-plugin-extension-changes.yml +++ b/.github/workflows/detect-plugin-extension-changes.yml @@ -36,7 +36,7 @@ jobs: - name: Check for plugin extension changes id: check-changes - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const { execSync } = require('child_process'); diff --git a/.github/workflows/pr-patch-check.yml b/.github/workflows/pr-patch-check.yml index 5de7fcf89ed..52f75a05ff2 100644 --- a/.github/workflows/pr-patch-check.yml +++ b/.github/workflows/pr-patch-check.yml @@ -55,7 +55,7 @@ jobs: permissions: "{\"actions\": \"write\", \"workflows\": \"write\"}" repositories: "[\"security-patch-actions\"]" - name: "Dispatch job" - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: github-token: ${{ steps.generate_token.outputs.token }} script: | diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 650ba6a67c1..ded468b0110 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -96,7 +96,7 @@ jobs: private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} repositories: '["grafana-enterprise"]' permissions: '{"actions": "write"}' - - uses: actions/github-script@v7 + - uses: actions/github-script@v8 env: REF: ${{ github.ref_name }} VERSION: ${{ needs.setup.outputs.version }} diff --git a/.github/workflows/sync-mirror-event.yml b/.github/workflows/sync-mirror-event.yml index 4a073c9b2b3..64d5c49a96d 100644 --- a/.github/workflows/sync-mirror-event.yml +++ b/.github/workflows/sync-mirror-event.yml @@ -41,7 +41,7 @@ jobs: app_id: ${{ vars.DELIVERY_BOT_APP_ID }} private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} - - uses: actions/github-script@v7 + - uses: actions/github-script@v8 if: github.repository == 'grafana/grafana' with: github-token: ${{ steps.generate_token.outputs.token }} From b7606951e9cabbba94bb23257cde55413b57d789 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:41:27 +0000 Subject: [PATCH 51/75] deps(actions): bump trufflesecurity/trufflehog from 3.90.0 to 3.90.9 (#112458) Bumps [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog) from 3.90.0 to 3.90.9. - [Release notes](https://github.com/trufflesecurity/trufflehog/releases) - [Changelog](https://github.com/trufflesecurity/trufflehog/blob/main/.goreleaser.yml) - [Commits](https://github.com/trufflesecurity/trufflehog/compare/eafb8c5f6a06175141c27f17bcc17941853d0047...e88e7d019eb84ca64f6b768c82eb6bf1a6271401) --- updated-dependencies: - dependency-name: trufflesecurity/trufflehog dependency-version: 3.90.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/trufflehog.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trufflehog.yml b/.github/workflows/trufflehog.yml index d11186c5343..b045df5a144 100644 --- a/.github/workflows/trufflehog.yml +++ b/.github/workflows/trufflehog.yml @@ -31,6 +31,6 @@ jobs: persist-credentials: false fetch-depth: ${{ steps.fetch_depth.outputs.fetch_depth }} - name: Trufflehog - uses: trufflesecurity/trufflehog@eafb8c5f6a06175141c27f17bcc17941853d0047 # v3.90.0 + uses: trufflesecurity/trufflehog@e88e7d019eb84ca64f6b768c82eb6bf1a6271401 # v3.90.9 with: extra_args: --results=verified From ba269186e812e845b16e123d6789e22e94f92ad5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:41:53 +0000 Subject: [PATCH 52/75] deps(actions): bump google-github-actions/auth from 2.1.7 to 3.0.0 (#112457) Bumps [google-github-actions/auth](https://github.com/google-github-actions/auth) from 2.1.7 to 3.0.0. - [Release notes](https://github.com/google-github-actions/auth/releases) - [Changelog](https://github.com/google-github-actions/auth/blob/main/CHANGELOG.md) - [Commits](https://github.com/google-github-actions/auth/compare/6fc4af4b145ae7821d527454aa9bd537d1f2dc5f...7c6bc770dae815cd3e89ee6cdf493a5fab2cc093) --- updated-dependencies: - dependency-name: google-github-actions/auth dependency-version: 3.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/core-plugins-build-and-release.yml | 2 +- .github/workflows/detect-breaking-changes-levitate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/core-plugins-build-and-release.yml b/.github/workflows/core-plugins-build-and-release.yml index 22836db5089..91c6f0485f6 100644 --- a/.github/workflows/core-plugins-build-and-release.yml +++ b/.github/workflows/core-plugins-build-and-release.yml @@ -58,7 +58,7 @@ jobs: PLUGINS_GRAFANA_API_KEY=core-plugins-build-and-release:PLUGINS_GRAFANA_API_KEY PLUGINS_GCOM_TOKEN=core-plugins-build-and-release:PLUGINS_GCOM_TOKEN - name: 'Authenticate to Google Cloud' - uses: 'google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f' + uses: 'google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093' with: credentials_json: '${{ env.PLUGINS_GOOGLE_CREDENTIALS }}' - name: 'Set up Cloud SDK' diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index ce2f195c026..a646c1983a2 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -157,7 +157,7 @@ jobs: run: unzip -j base_built_packages.zip -d ./base && rm base_built_packages.zip - id: 'auth' - uses: 'google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f' + uses: 'google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093' if: github.event.pull_request.head.repo.full_name == github.repository with: workload_identity_provider: projects/304398677251/locations/global/workloadIdentityPools/github/providers/github-provider From 3d3f62cf322e6ff41eee821ad4a2d684a348e220 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:52:55 +0000 Subject: [PATCH 53/75] Chore(deps): Bump dagger/dagger-for-github from 8.0.0 to 8.1.0 (#110919) Bumps [dagger/dagger-for-github](https://github.com/dagger/dagger-for-github) from 8.0.0 to 8.1.0. - [Release notes](https://github.com/dagger/dagger-for-github/releases) - [Commits](https://github.com/dagger/dagger-for-github/compare/e47aba410ef9bb9ed81a4d2a97df31061e5e842e...710906f3073901ccd9c8fd38e18848c00d9892be) --- updated-dependencies: - dependency-name: dagger/dagger-for-github dependency-version: 8.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 045a5906002e818d4f720b94b10bf3c839691811 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe <tom.ratcliffe@grafana.com> Date: Wed, 15 Oct 2025 19:54:57 +0100 Subject: [PATCH 54/75] Chore: Fix broken test after markdown move (#112474) --- scripts/docs/generate-transformations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/docs/generate-transformations.ts b/scripts/docs/generate-transformations.ts index bc13c8cb413..f6912c9a660 100644 --- a/scripts/docs/generate-transformations.ts +++ b/scripts/docs/generate-transformations.ts @@ -7,7 +7,7 @@ import { ImageRenderType, } from '../../public/app/features/transformers/docs/content'; -const WRITE_PATH = 'docs/sources/panels-visualizations/query-transform-data/transform-data/index.md'; +const WRITE_PATH = 'docs/sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md'; export const readMeContent = ` To update this Markdown, navigate to the following Typescript files and edit them based on what you need to update: From 4f0f09784bdd808e5c1e03120adfd096fe9ef9fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:42:38 +0000 Subject: [PATCH 55/75] deps(go): bump github.com/urfave/cli/v3 from 3.3.3 to 3.4.1 (#112467) Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.3.3 to 3.4.1. - [Release notes](https://github.com/urfave/cli/releases) - [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md) - [Commits](https://github.com/urfave/cli/compare/v3.3.3...v3.4.1) --- updated-dependencies: - dependency-name: github.com/urfave/cli/v3 dependency-version: 3.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- pkg/build/go.mod | 2 +- pkg/build/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 8754c16fc81..536cb715e49 100644 --- a/go.mod +++ b/go.mod @@ -175,7 +175,7 @@ require ( github.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90 // @grafana/grafana-backend-group github.com/urfave/cli v1.22.17 // indirect; @grafana/grafana-backend-group github.com/urfave/cli/v2 v2.27.7 // @grafana/grafana-backend-group - github.com/urfave/cli/v3 v3.3.3 // @grafana/grafana-backend-group + github.com/urfave/cli/v3 v3.4.1 // @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/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // @grafana/grafana-operator-experience-squad diff --git a/go.sum b/go.sum index acb8cdb6a37..fc2e6ef41f8 100644 --- a/go.sum +++ b/go.sum @@ -2479,8 +2479,8 @@ github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= -github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= -github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= +github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM= +github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= 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/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= diff --git a/pkg/build/go.mod b/pkg/build/go.mod index 2e6af12d380..0cd1120fb8f 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -38,7 +38,7 @@ require ( dagger.io/dagger v0.18.8 github.com/Masterminds/semver v1.5.0 github.com/quasilyte/go-ruleguard/dsl v0.3.22 - github.com/urfave/cli/v3 v3.3.3 + github.com/urfave/cli/v3 v3.4.1 ) require ( diff --git a/pkg/build/go.sum b/pkg/build/go.sum index 881acfbb52d..d538862b454 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -53,8 +53,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= -github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= -github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= +github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM= +github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s= github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= From 4f4174a3d8439ab0f6625804305bafc3e605fc99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 20:03:58 +0000 Subject: [PATCH 56/75] deps(go): bump github.com/grafana/pyroscope-go/godeltaprof from 0.1.8 to 0.1.9 (#112464) * deps(go): bump github.com/grafana/pyroscope-go/godeltaprof Bumps [github.com/grafana/pyroscope-go/godeltaprof](https://github.com/grafana/pyroscope-go) from 0.1.8 to 0.1.9. - [Release notes](https://github.com/grafana/pyroscope-go/releases) - [Commits](https://github.com/grafana/pyroscope-go/compare/godeltaprof/v0.1.8...godeltaprof/v0.1.9) --- updated-dependencies: - dependency-name: github.com/grafana/pyroscope-go/godeltaprof dependency-version: 0.1.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * make update-workspace Signed-off-by: Dave Henderson <dave.henderson@grafana.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: Dave Henderson <dave.henderson@grafana.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dave Henderson <dave.henderson@grafana.com> --- apps/dashboard/go.mod | 2 +- apps/dashboard/go.sum | 4 ++-- apps/iam/go.mod | 2 +- apps/iam/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- go.work.sum | 3 +-- pkg/aggregator/go.mod | 2 +- pkg/aggregator/go.sum | 4 ++-- pkg/promlib/go.mod | 2 +- pkg/promlib/go.sum | 4 ++-- 11 files changed, 16 insertions(+), 17 deletions(-) diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index 69ce9f376f8..45c5d33ffff 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -54,7 +54,7 @@ require ( github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index 9f18f73b002..f0102d46431 100644 --- a/apps/dashboard/go.sum +++ b/apps/dashboard/go.sum @@ -111,8 +111,8 @@ github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= -github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= -github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 2514467aef1..7120930feba 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -238,7 +238,7 @@ require ( github.com/grafana/grafana/pkg/promlib v0.0.8 // indirect github.com/grafana/grafana/pkg/semconv v0.0.0-20250804150913-990f1c69ecc2 // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grafana/sqlds/v4 v4.2.7 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 3ea1d3638b5..16a975c35ef 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -856,8 +856,8 @@ github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 h1:aXfUhVN/Ewfpbko2CCtL65cIiGgwStOo4lWH2b6gw2U= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= -github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= -github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae h1:35W3Wjp9KWnSoV/DuymmyIj5aHE0CYlDQ5m2KeXUPAc= github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae/go.mod h1:6CJ1uXmLZ13ufpO9xE4pST+DyaBt0uszzrV0YnoaVLQ= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= diff --git a/go.mod b/go.mod index 536cb715e49..30578e6382d 100644 --- a/go.mod +++ b/go.mod @@ -107,7 +107,7 @@ require ( github.com/grafana/loki/v3 v3.2.1 // @grafana/observability-logs github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 // indirect; @grafana/grafana-git-ui-sync-team github.com/grafana/otel-profiling-go v0.5.1 // @grafana/grafana-backend-group - github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // @grafana/observability-traces-and-profiling + github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // @grafana/observability-traces-and-profiling github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae // @grafana/observability-traces-and-profiling github.com/grafana/tempo v1.5.1-0.20250529124718-87c2dc380cec // @grafana/observability-traces-and-profiling github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // @grafana/grafana-search-and-storage diff --git a/go.sum b/go.sum index fc2e6ef41f8..48790382e52 100644 --- a/go.sum +++ b/go.sum @@ -1637,8 +1637,8 @@ github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 h1:aXfUhVN/Ewfpbko2CCtL65cIiGgwStOo4lWH2b6gw2U= github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= -github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= -github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae h1:35W3Wjp9KWnSoV/DuymmyIj5aHE0CYlDQ5m2KeXUPAc= github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae/go.mod h1:6CJ1uXmLZ13ufpO9xE4pST+DyaBt0uszzrV0YnoaVLQ= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= diff --git a/go.work.sum b/go.work.sum index 4ad9d238375..f179cb4ce52 100644 --- a/go.work.sum +++ b/go.work.sum @@ -988,7 +988,6 @@ github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9 github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= -github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= 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.3.0 h1:PVRnTgtArZ3QQqTGtbtjtnIkzl2iY2kt24yqbrf7td8= @@ -1063,7 +1062,6 @@ github.com/grafana/dskit v0.0.0-20250818234656-8ff9c6532e85/go.mod h1:kImsvJ1xnm github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak= github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90= github.com/grafana/gomemcache v0.0.0-20250228145437-da7b95fd2ac1/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= -github.com/grafana/gomemcache v0.0.0-20250828162811-a96f6acee2fe/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM= github.com/grafana/grafana-app-sdk v0.41.0 h1:SYHN3U7B1myRKY3UZZDkFsue9TDmAOap0UrQVTqtYBU= github.com/grafana/grafana-app-sdk v0.41.0/go.mod h1:Wg/3vEZfok1hhIWiHaaJm+FwkosfO98o8KbeLFEnZpY= @@ -1230,6 +1228,7 @@ github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHz github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw2e+eeNT/SbGySq8ajECXJ9e4fPoLhY= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index 75f4c09bb8e..29fde04efba 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -63,7 +63,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index 385d20d5cc7..697de9826ba 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -127,8 +127,8 @@ github.com/grafana/grafana/pkg/semconv v0.0.0-20250514132646-acbc7b54ed9e h1:vhe github.com/grafana/grafana/pkg/semconv v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:96h4xqAlVxlz+d7ZvocyxED/HojMyed0dD7z7tN76Yk= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= -github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= -github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 2bbeb8f9416..72330a91c94 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -56,7 +56,7 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251007081214-26e147d01f0a // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 91592bfd3ba..e8e8dc7ada0 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -140,8 +140,8 @@ github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251007081214-26e147d01f0a h github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251007081214-26e147d01f0a/go.mod h1:OK8NwS87D5YphchOcAsiIWk/feMZ0EzfAGME1Kff860= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= -github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= -github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= +github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= From 39031948b83e56a114ea4d9962e25b5b9ce6c1ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 20:07:00 +0000 Subject: [PATCH 57/75] deps(go): bump golang.org/x/text from 0.29.0 to 0.30.0 (#112462) * deps(go): bump golang.org/x/text from 0.29.0 to 0.30.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.29.0 to 0.30.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.29.0...v0.30.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.30.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * make update-workspace Signed-off-by: Dave Henderson <dave.henderson@grafana.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: Dave Henderson <dave.henderson@grafana.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dave Henderson <dave.henderson@grafana.com> --- apps/alerting/alertenrichment/go.mod | 2 +- apps/alerting/alertenrichment/go.sum | 4 ++-- apps/alerting/notifications/go.mod | 2 +- apps/alerting/notifications/go.sum | 4 ++-- apps/alerting/rules/go.mod | 2 +- apps/alerting/rules/go.sum | 4 ++-- apps/correlations/go.mod | 2 +- apps/correlations/go.sum | 4 ++-- apps/dashboard/go.mod | 2 +- apps/dashboard/go.sum | 4 ++-- apps/folder/go.mod | 2 +- apps/folder/go.sum | 4 ++-- apps/iam/go.mod | 2 +- apps/iam/go.sum | 4 ++-- apps/investigations/go.mod | 2 +- apps/investigations/go.sum | 4 ++-- apps/playlist/go.mod | 2 +- apps/playlist/go.sum | 4 ++-- apps/plugins/go.mod | 2 +- apps/plugins/go.sum | 4 ++-- apps/preferences/go.mod | 2 +- apps/preferences/go.sum | 4 ++-- apps/provisioning/go.mod | 2 +- apps/provisioning/go.sum | 4 ++-- apps/scope/go.mod | 2 +- apps/scope/go.sum | 4 ++-- apps/secret/go.mod | 2 +- apps/secret/go.sum | 4 ++-- apps/shorturl/go.mod | 2 +- apps/shorturl/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- go.work.sum | 1 + pkg/aggregator/go.mod | 2 +- pkg/aggregator/go.sum | 4 ++-- pkg/apimachinery/go.mod | 2 +- pkg/apimachinery/go.sum | 4 ++-- pkg/apiserver/go.mod | 3 +-- pkg/apiserver/go.sum | 4 ++-- pkg/build/go.mod | 2 +- pkg/build/go.sum | 4 ++-- pkg/codegen/go.mod | 2 +- pkg/codegen/go.sum | 4 ++-- pkg/plugins/codegen/go.mod | 2 +- pkg/plugins/codegen/go.sum | 4 ++-- pkg/promlib/go.mod | 2 +- pkg/promlib/go.sum | 4 ++-- 47 files changed, 70 insertions(+), 70 deletions(-) diff --git a/apps/alerting/alertenrichment/go.mod b/apps/alerting/alertenrichment/go.mod index 99e4446e23b..60b6d949e5a 100644 --- a/apps/alerting/alertenrichment/go.mod +++ b/apps/alerting/alertenrichment/go.mod @@ -27,7 +27,7 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/net v0.45.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect google.golang.org/protobuf v1.36.9 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/apps/alerting/alertenrichment/go.sum b/apps/alerting/alertenrichment/go.sum index 96dc13e50a2..6e0e8323e5a 100644 --- a/apps/alerting/alertenrichment/go.sum +++ b/apps/alerting/alertenrichment/go.sum @@ -81,8 +81,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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= diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index 6907a28720a..ed8528aa92f 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -88,7 +88,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index b613313ed23..2df3420d2b7 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -256,8 +256,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/alerting/rules/go.mod b/apps/alerting/rules/go.mod index c51dbdeeef3..3e7a20b4e6c 100644 --- a/apps/alerting/rules/go.mod +++ b/apps/alerting/rules/go.mod @@ -71,7 +71,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect diff --git a/apps/alerting/rules/go.sum b/apps/alerting/rules/go.sum index ff48e3193e0..f50f1d3b2ce 100644 --- a/apps/alerting/rules/go.sum +++ b/apps/alerting/rules/go.sum @@ -179,8 +179,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/correlations/go.mod b/apps/correlations/go.mod index 212391d0468..7f6142dc80e 100644 --- a/apps/correlations/go.mod +++ b/apps/correlations/go.mod @@ -71,7 +71,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect diff --git a/apps/correlations/go.sum b/apps/correlations/go.sum index ff48e3193e0..f50f1d3b2ce 100644 --- a/apps/correlations/go.sum +++ b/apps/correlations/go.sum @@ -179,8 +179,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index 45c5d33ffff..66ffbfb21ed 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -127,7 +127,7 @@ require ( golang.org/x/sys v0.36.0 // indirect golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect golang.org/x/tools v0.37.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index f0102d46431..e944b178d35 100644 --- a/apps/dashboard/go.sum +++ b/apps/dashboard/go.sum @@ -362,8 +362,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/folder/go.mod b/apps/folder/go.mod index daf5783d343..acec11443c3 100644 --- a/apps/folder/go.mod +++ b/apps/folder/go.mod @@ -53,7 +53,7 @@ require ( golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect google.golang.org/protobuf v1.36.9 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/apps/folder/go.sum b/apps/folder/go.sum index 68909c968ec..3a0ba21beb5 100644 --- a/apps/folder/go.sum +++ b/apps/folder/go.sum @@ -140,8 +140,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 7120930feba..1a6c01249d4 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -433,7 +433,7 @@ require ( golang.org/x/sys v0.36.0 // indirect golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect golang.org/x/tools v0.37.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 16a975c35ef..7b8a26150cb 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -1938,8 +1938,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= 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= diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod index 2e2704b8e67..778776ed2de 100644 --- a/apps/investigations/go.mod +++ b/apps/investigations/go.mod @@ -72,7 +72,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum index ff48e3193e0..f50f1d3b2ce 100644 --- a/apps/investigations/go.sum +++ b/apps/investigations/go.sum @@ -179,8 +179,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index 02cf3943f6f..cca459faea2 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -73,7 +73,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index ff48e3193e0..f50f1d3b2ce 100644 --- a/apps/playlist/go.sum +++ b/apps/playlist/go.sum @@ -179,8 +179,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod index 7c803ef1e15..175f5d0c3e4 100644 --- a/apps/plugins/go.mod +++ b/apps/plugins/go.mod @@ -80,7 +80,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum index 16411aeecb3..91ea5ada338 100644 --- a/apps/plugins/go.sum +++ b/apps/plugins/go.sum @@ -195,8 +195,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/preferences/go.mod b/apps/preferences/go.mod index 932af3adf61..29d1b1035af 100644 --- a/apps/preferences/go.mod +++ b/apps/preferences/go.mod @@ -53,7 +53,7 @@ require ( golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect google.golang.org/protobuf v1.36.9 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/apps/preferences/go.sum b/apps/preferences/go.sum index 0bca000ff09..d2b6077a6cb 100644 --- a/apps/preferences/go.sum +++ b/apps/preferences/go.sum @@ -140,8 +140,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/provisioning/go.mod b/apps/provisioning/go.mod index e30a0b77cc7..4e1540512ae 100644 --- a/apps/provisioning/go.mod +++ b/apps/provisioning/go.mod @@ -72,7 +72,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect google.golang.org/grpc v1.75.1 // indirect diff --git a/apps/provisioning/go.sum b/apps/provisioning/go.sum index ce659fcf611..c8a60fb705f 100644 --- a/apps/provisioning/go.sum +++ b/apps/provisioning/go.sum @@ -204,8 +204,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/scope/go.mod b/apps/scope/go.mod index a6ed7ba83a1..691621a2a79 100644 --- a/apps/scope/go.mod +++ b/apps/scope/go.mod @@ -30,7 +30,7 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/net v0.45.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect google.golang.org/protobuf v1.36.9 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/apps/scope/go.sum b/apps/scope/go.sum index 7bf603654c8..6ac3c9dca1a 100644 --- a/apps/scope/go.sum +++ b/apps/scope/go.sum @@ -81,8 +81,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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= diff --git a/apps/secret/go.mod b/apps/secret/go.mod index 0ff5e5f02eb..dbadbada0ba 100644 --- a/apps/secret/go.mod +++ b/apps/secret/go.mod @@ -58,7 +58,7 @@ require ( golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/apps/secret/go.sum b/apps/secret/go.sum index 54152dfd80d..6a9315815a6 100644 --- a/apps/secret/go.sum +++ b/apps/secret/go.sum @@ -152,8 +152,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/apps/shorturl/go.mod b/apps/shorturl/go.mod index fe039294882..b408027adca 100644 --- a/apps/shorturl/go.mod +++ b/apps/shorturl/go.mod @@ -78,7 +78,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect diff --git a/apps/shorturl/go.sum b/apps/shorturl/go.sum index 5b7d01fbcd9..23f1820794d 100644 --- a/apps/shorturl/go.sum +++ b/apps/shorturl/go.sum @@ -193,8 +193,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/go.mod b/go.mod index 30578e6382d..2cc61e24725 100644 --- a/go.mod +++ b/go.mod @@ -205,7 +205,7 @@ require ( golang.org/x/net v0.45.0 // @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.30.0 // @grafana/identity-access-team golang.org/x/sync v0.17.0 // @grafana/alerting-backend - golang.org/x/text v0.29.0 // @grafana/grafana-backend-group + golang.org/x/text v0.30.0 // @grafana/grafana-backend-group golang.org/x/time v0.13.0 // @grafana/grafana-backend-group golang.org/x/tools v0.37.0 // indirect; @grafana/grafana-as-code gonum.org/v1/gonum v0.16.0 // @grafana/oss-big-tent diff --git a/go.sum b/go.sum index 48790382e52..9d2dc01279a 100644 --- a/go.sum +++ b/go.sum @@ -3119,8 +3119,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= 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= diff --git a/go.work.sum b/go.work.sum index f179cb4ce52..e2efefd6800 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2035,6 +2035,7 @@ golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index 29fde04efba..6d0689a0fd5 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -146,7 +146,7 @@ require ( golang.org/x/sys v0.36.0 // indirect golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect golang.org/x/tools v0.37.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index 697de9826ba..5553a051ded 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -417,8 +417,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 79aeb81a53b..f6938545351 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -48,7 +48,7 @@ require ( golang.org/x/net v0.45.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect google.golang.org/grpc v1.75.1 // indirect google.golang.org/protobuf v1.36.9 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 8cae112de9f..7e2b7cf0841 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -116,8 +116,8 @@ golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 29ebeb8ebf5..c57482ac029 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -89,9 +89,8 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect - golang.org/x/tools v0.37.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect google.golang.org/grpc v1.75.1 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index a5fb4f6048d..ffc6184be01 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -239,8 +239,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/build/go.mod b/pkg/build/go.mod index 0cd1120fb8f..84b82b87c3a 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -15,7 +15,7 @@ require ( go.opentelemetry.io/otel/trace v1.38.0 // indirect; @grafana/grafana-backend-group golang.org/x/net v0.45.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/sync v0.17.0 // @grafana/alerting-backend - golang.org/x/text v0.29.0 // indirect; @grafana/grafana-backend-group + golang.org/x/text v0.30.0 // indirect; @grafana/grafana-backend-group google.golang.org/grpc v1.75.1 // indirect; @grafana/plugins-platform-backend google.golang.org/protobuf v1.36.9 // indirect; @grafana/plugins-platform-backend ) diff --git a/pkg/build/go.sum b/pkg/build/go.sum index d538862b454..d93bfc215f6 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -101,8 +101,8 @@ golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ= diff --git a/pkg/codegen/go.mod b/pkg/codegen/go.mod index 8af76404f0a..abbd06e4991 100644 --- a/pkg/codegen/go.mod +++ b/pkg/codegen/go.mod @@ -50,7 +50,7 @@ require ( golang.org/x/mod v0.28.0 // indirect golang.org/x/net v0.45.0 // indirect golang.org/x/sync v0.17.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/codegen/go.sum b/pkg/codegen/go.sum index 0e5105a308e..e3098c9e19f 100644 --- a/pkg/codegen/go.sum +++ b/pkg/codegen/go.sum @@ -106,8 +106,8 @@ golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/plugins/codegen/go.mod b/pkg/plugins/codegen/go.mod index c0d245bf3c3..976ec93d2b7 100644 --- a/pkg/plugins/codegen/go.mod +++ b/pkg/plugins/codegen/go.mod @@ -47,7 +47,7 @@ require ( golang.org/x/net v0.45.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.17.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/tools v0.37.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/plugins/codegen/go.sum b/pkg/plugins/codegen/go.sum index d0de427e679..18d8f6b9971 100644 --- a/pkg/plugins/codegen/go.sum +++ b/pkg/plugins/codegen/go.sum @@ -104,8 +104,8 @@ golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= 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 72330a91c94..4f437fb9dff 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -125,7 +125,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect golang.org/x/tools v0.37.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index e8e8dc7ada0..47f815b43a6 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -399,8 +399,8 @@ golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 h1:dHQOQddU4YHS5gY33/6 golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE= 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 5a516197b65067764e308b43fa0c29b10036ba4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 20:21:07 +0000 Subject: [PATCH 58/75] Bump github.com/mocktools/go-smtp-mock/v2 from 2.3.1 to 2.5.1 (#110377) Bumps [github.com/mocktools/go-smtp-mock/v2](https://github.com/mocktools/go-smtp-mock) from 2.3.1 to 2.5.1. - [Release notes](https://github.com/mocktools/go-smtp-mock/releases) - [Changelog](https://github.com/mocktools/go-smtp-mock/blob/master/CHANGELOG.md) - [Commits](https://github.com/mocktools/go-smtp-mock/compare/v2.3.1...v2.5.1) --- updated-dependencies: - dependency-name: github.com/mocktools/go-smtp-mock/v2 dependency-version: 2.5.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/iam/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 7b8a26150cb..82d2de5e31c 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -1177,8 +1177,8 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/mocktools/go-smtp-mock/v2 v2.3.1 h1:wq75NDSsOy5oHo/gEQQT0fRRaYKRqr1IdkjhIPXxagM= -github.com/mocktools/go-smtp-mock/v2 v2.3.1/go.mod h1:h9AOf/IXLSU2m/1u4zsjtOM/WddPwdOUBz56dV9f81M= +github.com/mocktools/go-smtp-mock/v2 v2.5.1 h1:QcMJMChSgG1olVj4o6xxQFdrWzRjYNrcq660HAjd0wA= +github.com/mocktools/go-smtp-mock/v2 v2.5.1/go.mod h1:Rr8M2njlxx//l5INl2+uESnsL2lDsL24teEykCrGfmE= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/go.mod b/go.mod index 2cc61e24725..861b827cc80 100644 --- a/go.mod +++ b/go.mod @@ -140,7 +140,7 @@ require ( github.com/migueleliasweb/go-github-mock v1.1.0 // @grafana/grafana-git-ui-sync-team github.com/mitchellh/copystructure v1.2.0 // @grafana/grafana-operator-experience-squad github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c //@grafana/identity-access-team - github.com/mocktools/go-smtp-mock/v2 v2.3.1 // @grafana/grafana-backend-group + github.com/mocktools/go-smtp-mock/v2 v2.5.1 // @grafana/grafana-backend-group github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // @grafana/alerting-backend github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // @grafana/grafana-operator-experience-squad github.com/olekukonko/tablewriter v0.0.5 // @grafana/grafana-backend-group diff --git a/go.sum b/go.sum index 9d2dc01279a..56c96741801 100644 --- a/go.sum +++ b/go.sum @@ -2054,8 +2054,8 @@ github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5 github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/mocktools/go-smtp-mock/v2 v2.3.1 h1:wq75NDSsOy5oHo/gEQQT0fRRaYKRqr1IdkjhIPXxagM= -github.com/mocktools/go-smtp-mock/v2 v2.3.1/go.mod h1:h9AOf/IXLSU2m/1u4zsjtOM/WddPwdOUBz56dV9f81M= +github.com/mocktools/go-smtp-mock/v2 v2.5.1 h1:QcMJMChSgG1olVj4o6xxQFdrWzRjYNrcq660HAjd0wA= +github.com/mocktools/go-smtp-mock/v2 v2.5.1/go.mod h1:Rr8M2njlxx//l5INl2+uESnsL2lDsL24teEykCrGfmE= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= From ae5ff7e8f0ed32c4e23bb13db43d9ea31113b9eb Mon Sep 17 00:00:00 2001 From: Mihai Turdean <6640685+mihai-turdean@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:27:59 -0600 Subject: [PATCH 59/75] Implement CoreRole Authorizer (#112401) --- pkg/registry/apis/iam/authorizer.go | 3 +- pkg/registry/apis/iam/authorizer/core_role.go | 65 +++ .../apis/iam/authorizer/core_role_test.go | 408 ++++++++++++++++++ pkg/services/authz/rbac/mapper.go | 12 +- 4 files changed, 486 insertions(+), 2 deletions(-) create mode 100644 pkg/registry/apis/iam/authorizer/core_role.go create mode 100644 pkg/registry/apis/iam/authorizer/core_role_test.go diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index 4fe350954ad..439f0de2da5 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -9,6 +9,7 @@ import ( iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" + iamauthorizer "github.com/grafana/grafana/pkg/registry/apis/iam/authorizer" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" "github.com/grafana/grafana/pkg/services/accesscontrol" gfauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" @@ -29,7 +30,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth // Access specific resources authorizer := gfauthorizer.NewResourceAuthorizer(accessClient) - resourceAuthorizer[iamv0.CoreRoleInfo.GetName()] = authorizer + resourceAuthorizer[iamv0.CoreRoleInfo.GetName()] = iamauthorizer.NewCoreRoleAuthorizer(accessClient) resourceAuthorizer[iamv0.RoleInfo.GetName()] = authorizer resourceAuthorizer[iamv0.ResourcePermissionInfo.GetName()] = authorizer resourceAuthorizer[iamv0.RoleBindingInfo.GetName()] = authorizer diff --git a/pkg/registry/apis/iam/authorizer/core_role.go b/pkg/registry/apis/iam/authorizer/core_role.go new file mode 100644 index 00000000000..c133765b830 --- /dev/null +++ b/pkg/registry/apis/iam/authorizer/core_role.go @@ -0,0 +1,65 @@ +package authorizer + +import ( + "context" + "fmt" + + "k8s.io/apiserver/pkg/authorization/authorizer" + + claims "github.com/grafana/authlib/types" + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" +) + +var _ authorizer.Authorizer = &coreRoleAuthorizer{} + +type coreRoleAuthorizer struct { + c claims.AccessClient +} + +func NewCoreRoleAuthorizer(c claims.AccessClient) *coreRoleAuthorizer { + return &coreRoleAuthorizer{c} +} + +func (r coreRoleAuthorizer) Authorize(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) { + if attr.GetResource() != iamv0.CoreRoleInfo.GetName() { + return authorizer.DecisionDeny, "", fmt.Errorf("unauthorized request: resource %s is not allowed by core role authorizer", attr.GetResource()) + } + + ident, ok := claims.AuthInfoFrom(ctx) + if !ok { + return authorizer.DecisionDeny, "", fmt.Errorf("unauthorized request: no identity found for request") + } + + verb := attr.GetVerb() + isReadVerb := verb == utils.VerbGet || verb == utils.VerbList || verb == utils.VerbWatch + identityType := ident.GetIdentityType() + isAccessPolicy := identityType == claims.TypeAccessPolicy + + if !isAccessPolicy && !isReadVerb { + return authorizer.DecisionDeny, fmt.Sprintf( + "unauthorized request: identity type %s is not allowed for verb %s", + identityType, verb, + ), nil + } + + res, err := r.c.Check(ctx, ident, claims.CheckRequest{ + Verb: verb, + Group: attr.GetAPIGroup(), + Resource: attr.GetResource(), + Namespace: attr.GetNamespace(), + Name: attr.GetName(), + Subresource: attr.GetSubresource(), + Path: attr.GetPath(), + }, "") + + if err != nil { + return authorizer.DecisionDeny, "", err + } + + if !res.Allowed { + return authorizer.DecisionDeny, "unauthorized request", nil + } + + return authorizer.DecisionAllow, "", nil +} diff --git a/pkg/registry/apis/iam/authorizer/core_role_test.go b/pkg/registry/apis/iam/authorizer/core_role_test.go new file mode 100644 index 00000000000..fd9b4487163 --- /dev/null +++ b/pkg/registry/apis/iam/authorizer/core_role_test.go @@ -0,0 +1,408 @@ +package authorizer + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apiserver/pkg/authorization/authorizer" + + claims "github.com/grafana/authlib/types" + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" +) + +func TestNewCoreRoleAuthorizer(t *testing.T) { + mockClient := &mockAccessClient{} + auth := NewCoreRoleAuthorizer(mockClient) + + require.NotNil(t, auth) + assert.Equal(t, mockClient, auth.c) +} + +func TestCoreRoleAuthorizer_Authorize(t *testing.T) { + t.Run("wrong resource returns deny with error", func(t *testing.T) { + mockClient := &mockAccessClient{} + auth := NewCoreRoleAuthorizer(mockClient) + + attrs := &fakeCoreAttributes{ + resource: "playlists", + } + + decision, reason, err := auth.Authorize(context.Background(), attrs) + + assert.Equal(t, authorizer.DecisionDeny, decision) + assert.Empty(t, reason) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unauthorized request: resource playlists is not allowed") + }) + + t.Run("missing identity returns deny with error", func(t *testing.T) { + mockClient := &mockAccessClient{} + auth := NewCoreRoleAuthorizer(mockClient) + + attrs := &fakeCoreAttributes{ + resource: iamv0.CoreRoleInfo.GetName(), + } + + decision, reason, err := auth.Authorize(context.Background(), attrs) + + assert.Equal(t, authorizer.DecisionDeny, decision) + assert.Empty(t, reason) + assert.Error(t, err) + assert.Equal(t, "unauthorized request: no identity found for request", err.Error()) + }) + + t.Run("non-access-policy identity with write verbs returns deny", func(t *testing.T) { + testCases := []struct { + name string + identityType claims.IdentityType + verb string + }{ + { + name: "user with create", + identityType: claims.TypeUser, + verb: utils.VerbCreate, + }, + { + name: "service account with update", + identityType: claims.TypeServiceAccount, + verb: utils.VerbUpdate, + }, + { + name: "api key with delete", + identityType: claims.TypeAPIKey, + verb: utils.VerbDelete, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockClient := &mockAccessClient{} + auth := NewCoreRoleAuthorizer(mockClient) + + authInfo := &mockAuthInfo{ + identityType: tc.identityType, + } + ctx := claims.WithAuthInfo(context.Background(), authInfo) + + attrs := &fakeCoreAttributes{ + resource: iamv0.CoreRoleInfo.GetName(), + verb: tc.verb, + } + + decision, reason, err := auth.Authorize(ctx, attrs) + + assert.Equal(t, authorizer.DecisionDeny, decision) + assert.Contains(t, reason, "unauthorized request: identity type") + assert.Contains(t, reason, string(tc.identityType)) + assert.Contains(t, reason, tc.verb) + assert.NoError(t, err) + }) + } + }) + + t.Run("non-access-policy identity with read verbs calls Check", func(t *testing.T) { + testCases := []struct { + name string + identityType claims.IdentityType + verb string + }{ + { + name: "user with get", + identityType: claims.TypeUser, + verb: utils.VerbGet, + }, + { + name: "user with list", + identityType: claims.TypeUser, + verb: utils.VerbList, + }, + { + name: "service account with watch", + identityType: claims.TypeServiceAccount, + verb: utils.VerbWatch, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockClient := &mockAccessClient{ + checkResponse: claims.CheckResponse{Allowed: true}, + } + auth := NewCoreRoleAuthorizer(mockClient) + + authInfo := &mockAuthInfo{ + identityType: tc.identityType, + } + ctx := claims.WithAuthInfo(context.Background(), authInfo) + + attrs := &fakeCoreAttributes{ + verb: tc.verb, + apiGroup: iamv0.GROUP, + resource: iamv0.CoreRoleInfo.GetName(), + namespace: "default", + name: "my-corerole", + subresource: "status", + path: "/apis/iam.grafana.app/v0alpha1/namespaces/default/coreroles/my-corerole/status", + } + + decision, reason, err := auth.Authorize(ctx, attrs) + + assert.Equal(t, authorizer.DecisionAllow, decision) + assert.Empty(t, reason) + assert.NoError(t, err) + require.NotNil(t, mockClient.lastCheckRequest) + assert.Equal(t, tc.verb, mockClient.lastCheckRequest.Verb) + assert.Equal(t, iamv0.GROUP, mockClient.lastCheckRequest.Group) + assert.Equal(t, iamv0.CoreRoleInfo.GetName(), mockClient.lastCheckRequest.Resource) + assert.Equal(t, "default", mockClient.lastCheckRequest.Namespace) + assert.Equal(t, "my-corerole", mockClient.lastCheckRequest.Name) + assert.Equal(t, "status", mockClient.lastCheckRequest.Subresource) + assert.Equal(t, "/apis/iam.grafana.app/v0alpha1/namespaces/default/coreroles/my-corerole/status", mockClient.lastCheckRequest.Path) + }) + } + }) + + t.Run("access-policy identity with any verb calls Check", func(t *testing.T) { + testCases := []struct { + name string + verb string + }{ + { + name: "create", + verb: utils.VerbCreate, + }, + { + name: "update", + verb: utils.VerbUpdate, + }, + { + name: "delete", + verb: utils.VerbDelete, + }, + { + name: "get", + verb: utils.VerbGet, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockClient := &mockAccessClient{ + checkResponse: claims.CheckResponse{Allowed: true}, + } + auth := NewCoreRoleAuthorizer(mockClient) + + authInfo := &mockAuthInfo{ + identityType: claims.TypeAccessPolicy, + } + ctx := claims.WithAuthInfo(context.Background(), authInfo) + + attrs := &fakeCoreAttributes{ + verb: tc.verb, + apiGroup: iamv0.GROUP, + resource: iamv0.CoreRoleInfo.GetName(), + } + + decision, reason, err := auth.Authorize(ctx, attrs) + + assert.Equal(t, authorizer.DecisionAllow, decision) + assert.Empty(t, reason) + assert.NoError(t, err) + require.NotNil(t, mockClient.lastCheckRequest) + assert.Equal(t, tc.verb, mockClient.lastCheckRequest.Verb) + }) + } + }) + + t.Run("Check returns error", func(t *testing.T) { + expectedErr := errors.New("check failed") + mockClient := &mockAccessClient{ + checkError: expectedErr, + } + auth := NewCoreRoleAuthorizer(mockClient) + + authInfo := &mockAuthInfo{ + identityType: claims.TypeAccessPolicy, + } + ctx := claims.WithAuthInfo(context.Background(), authInfo) + + attrs := &fakeCoreAttributes{ + resource: iamv0.CoreRoleInfo.GetName(), + verb: utils.VerbGet, + } + + decision, reason, err := auth.Authorize(ctx, attrs) + + assert.Equal(t, authorizer.DecisionDeny, decision) + assert.Empty(t, reason) + assert.Error(t, err) + assert.Equal(t, expectedErr, err) + }) + + t.Run("Check returns not allowed", func(t *testing.T) { + mockClient := &mockAccessClient{ + checkResponse: claims.CheckResponse{Allowed: false}, + } + auth := NewCoreRoleAuthorizer(mockClient) + + authInfo := &mockAuthInfo{ + identityType: claims.TypeAccessPolicy, + } + ctx := claims.WithAuthInfo(context.Background(), authInfo) + + attrs := &fakeCoreAttributes{ + resource: iamv0.CoreRoleInfo.GetName(), + verb: utils.VerbGet, + } + + decision, reason, err := auth.Authorize(ctx, attrs) + + assert.Equal(t, authorizer.DecisionDeny, decision) + assert.Equal(t, "unauthorized request", reason) + assert.NoError(t, err) + }) +} + +// mockAccessClient is a mock implementation of claims.AccessClient +type mockAccessClient struct { + checkResponse claims.CheckResponse + checkError error + lastCheckRequest *claims.CheckRequest +} + +func (m *mockAccessClient) Check(ctx context.Context, id claims.AuthInfo, req claims.CheckRequest, folder string) (claims.CheckResponse, error) { + m.lastCheckRequest = &req + if m.checkError != nil { + return claims.CheckResponse{}, m.checkError + } + return m.checkResponse, nil +} + +func (m *mockAccessClient) Compile(ctx context.Context, id claims.AuthInfo, req claims.ListRequest) (claims.ItemChecker, claims.Zookie, error) { + return nil, nil, nil +} + +// mockAuthInfo is a mock implementation of claims.AuthInfo +type mockAuthInfo struct { + identityType claims.IdentityType + namespace string + uid string + name string +} + +func (m *mockAuthInfo) GetUID() string { + if m.uid != "" { + return m.uid + } + return "test-uid" +} + +func (m *mockAuthInfo) GetIdentifier() string { + return "test-identifier" +} + +func (m *mockAuthInfo) GetIdentityType() claims.IdentityType { + return m.identityType +} + +func (m *mockAuthInfo) GetNamespace() string { + if m.namespace != "" { + return m.namespace + } + return "default" +} + +func (m *mockAuthInfo) GetGroups() []string { + return nil +} + +func (m *mockAuthInfo) GetExtra() map[string][]string { + return nil +} + +func (m *mockAuthInfo) GetSubject() string { + return "test-subject" +} + +func (m *mockAuthInfo) GetAudience() []string { + return nil +} + +func (m *mockAuthInfo) GetTokenPermissions() []string { + return nil +} + +func (m *mockAuthInfo) GetTokenDelegatedPermissions() []string { + return nil +} + +func (m *mockAuthInfo) GetName() string { + if m.name != "" { + return m.name + } + return "test-name" +} + +func (m *mockAuthInfo) GetEmail() string { + return "" +} + +func (m *mockAuthInfo) GetEmailVerified() bool { + return false +} + +func (m *mockAuthInfo) GetUsername() string { + return "" +} + +func (m *mockAuthInfo) GetAuthenticatedBy() string { + return "" +} + +func (m *mockAuthInfo) GetIDToken() string { + return "" +} + +// fakeCoreAttributes is a mock implementation of authorizer.Attributes +type fakeCoreAttributes struct { + authorizer.Attributes + verb string + apiGroup string + resource string + namespace string + name string + subresource string + path string +} + +func (a *fakeCoreAttributes) GetVerb() string { + return a.verb +} + +func (a *fakeCoreAttributes) GetAPIGroup() string { + return a.apiGroup +} + +func (a *fakeCoreAttributes) GetResource() string { + return a.resource +} + +func (a *fakeCoreAttributes) GetNamespace() string { + return a.namespace +} + +func (a *fakeCoreAttributes) GetName() string { + return a.name +} + +func (a *fakeCoreAttributes) GetSubresource() string { + return a.subresource +} + +func (a *fakeCoreAttributes) GetPath() string { + return a.path +} diff --git a/pkg/services/authz/rbac/mapper.go b/pkg/services/authz/rbac/mapper.go index 42bac84c6da..966dc214044 100644 --- a/pkg/services/authz/rbac/mapper.go +++ b/pkg/services/authz/rbac/mapper.go @@ -193,7 +193,17 @@ func NewMapperRegistry() MapperRegistry { // Teams is a special case. We translate user permissions from id to uid based. "teams": newResourceTranslation("teams", "uid", false, true), // No need to skip scope on create for roles because we translate `permissions:type:delegate` to `roles:*`` - "coreroles": newResourceTranslation("roles", "uid", false, false), + "coreroles": translation{ + resource: "roles", + attribute: "uid", + verbMapping: map[string]string{ + utils.VerbGet: "roles:read", + utils.VerbList: "roles:read", + utils.VerbWatch: "roles:read", + }, + folderSupport: false, + skipScopeOnCreate: false, + }, "roles": translation{ resource: "roles", attribute: "uid", From 4e84e76934bdddd043bfc5bc9b61c4fd4ea832a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:31:59 -0400 Subject: [PATCH 60/75] deps(go): bump modernc.org/sqlite from 1.38.2 to 1.39.1 (#112465) * deps(go): bump modernc.org/sqlite from 1.38.2 to 1.39.1 Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.38.2 to 1.39.1. - [Commits](https://gitlab.com/cznic/sqlite/compare/v1.38.2...v1.39.1) --- updated-dependencies: - dependency-name: modernc.org/sqlite dependency-version: 1.39.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * make update-workspace Signed-off-by: Dave Henderson <dave.henderson@grafana.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: Dave Henderson <dave.henderson@grafana.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dave Henderson <dave.henderson@grafana.com> --- apps/iam/go.mod | 4 ++-- apps/iam/go.sum | 20 ++++++++++---------- go.mod | 4 ++-- go.sum | 20 ++++++++++---------- go.work.sum | 2 ++ 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 1a6c01249d4..792df436c8d 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -464,10 +464,10 @@ require ( k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kms v0.34.1 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - modernc.org/libc v1.66.3 // indirect + modernc.org/libc v1.66.10 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.38.2 // indirect + modernc.org/sqlite v1.39.1 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 82d2de5e31c..eb5066756c1 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -2289,18 +2289,18 @@ k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOP k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= -modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= -modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= -modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= -modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= +modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= +modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q= +modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= +modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= -modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= +modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= @@ -2309,8 +2309,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= -modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= +modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4= +modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/go.mod b/go.mod index 861b827cc80..e0e2073c0a8 100644 --- a/go.mod +++ b/go.mod @@ -225,7 +225,7 @@ require ( k8s.io/kube-aggregator v0.34.1 // @grafana/grafana-app-platform-squad k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // @grafana/grafana-app-platform-squad k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // @grafana/partner-datasources - modernc.org/sqlite v1.38.2 // @grafana/grafana-backend-group + modernc.org/sqlite v1.39.1 // @grafana/grafana-backend-group pgregory.net/rapid v1.2.0 // @grafana/grafana-operator-experience-squad sigs.k8s.io/randfill v1.0.0 // @grafana/grafana-app-platform-squad sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // @grafana/grafana-app-platform-squad @@ -645,7 +645,7 @@ require ( gopkg.in/telebot.v3 v3.3.8 // indirect k8s.io/apiextensions-apiserver v0.34.1 // indirect k8s.io/kms v0.34.1 // indirect - modernc.org/libc v1.66.3 // indirect + modernc.org/libc v1.66.10 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect diff --git a/go.sum b/go.sum index 56c96741801..93feb28f947 100644 --- a/go.sum +++ b/go.sum @@ -3633,19 +3633,19 @@ lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= -modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= +modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= -modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= +modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= -modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= +modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= @@ -3658,8 +3658,8 @@ modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= -modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= +modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= @@ -3677,8 +3677,8 @@ modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= -modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= +modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4= +modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= diff --git a/go.work.sum b/go.work.sum index e2efefd6800..229ea24d850 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2235,6 +2235,8 @@ modernc.org/ccgo/v3 v3.17.0/go.mod h1:Sg3fwVpmLvCUTaqEUjiBDAvshIaKDB0RXaf+zgqFu8 modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/ccorpus2 v1.5.2 h1:Ui+4tc58mf/W+2arcYCJR903y3zl3ecsI7Fpaaqozyw= modernc.org/ccorpus2 v1.5.2/go.mod h1:Wifvo4Q/qS/h1aRoC2TffcHsnxwTikmi1AuLANuucJQ= +modernc.org/ccorpus2 v1.5.4 h1:k9A52f3NsUQzHStOav5ukvkdKz63CW5po4gaC5VH4Qc= +modernc.org/ccorpus2 v1.5.4/go.mod h1:Wifvo4Q/qS/h1aRoC2TffcHsnxwTikmi1AuLANuucJQ= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= From 3dceaac5dc24c3b2b2f7c8eb0c12b3d38808792c Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:32:25 -0400 Subject: [PATCH 61/75] fix pagination not working in legacy modes with kubernetes apis enabled (#112478) * dashboards: fix pagination not working in legacy modes with kubernetes apis enabled --- pkg/registry/apis/dashboard/search.go | 4 + pkg/registry/apis/dashboard/search_test.go | 92 ++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index 5b0e93a0444..7dba6e1f8f1 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -279,8 +279,12 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { } if queryParams.Has("offset") { offset, _ = strconv.Atoi(queryParams.Get("offset")) + if offset > 0 { + page = (offset / limit) + 1 + } } else if queryParams.Has("page") { page, _ = strconv.Atoi(queryParams.Get("page")) + offset = (page - 1) * limit } searchRequest := &resourcepb.ResourceSearchRequest{ diff --git a/pkg/registry/apis/dashboard/search_test.go b/pkg/registry/apis/dashboard/search_test.go index 4224d5927b3..b392cb1606c 100644 --- a/pkg/registry/apis/dashboard/search_test.go +++ b/pkg/registry/apis/dashboard/search_test.go @@ -189,6 +189,98 @@ func TestSearchFallback(t *testing.T) { }) } +func TestSearchHandlerPagination(t *testing.T) { + t.Run("should calculate offset and page parameters", func(t *testing.T) { + limit := 50 + for i, tt := range []struct { + offset int + page int + expectedOffset int + expectedPage int + }{ + { + offset: 0, + page: 0, + expectedOffset: 0, + expectedPage: 1, + }, + { + offset: 0, + page: 1, + expectedOffset: 0, + expectedPage: 1, + }, + { + offset: 0, + page: 2, + expectedOffset: 50, + expectedPage: 2, + }, + { + offset: 0, + page: 3, + expectedOffset: 100, + expectedPage: 3, + }, + { + offset: 50, + page: 0, + expectedOffset: 50, + expectedPage: 2, + }, + { + offset: 100, + page: 0, + expectedOffset: 100, + expectedPage: 3, + }, + { + offset: 149, + page: 0, + expectedOffset: 149, + expectedPage: 3, + }, + { + offset: 150, + page: 0, + expectedOffset: 150, + expectedPage: 4, + }, + } { + mockClient := &MockClient{} + + cfg := &setting.Cfg{ + UnifiedStorage: map[string]setting.UnifiedStorageConfig{ + "dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode0}, + }, + } + dual := dualwrite.ProvideStaticServiceForTests(cfg) + searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), dual, mockClient, mockClient, nil) + + rr := httptest.NewRecorder() + endpoint := fmt.Sprintf("/search?limit=%d", limit) + if tt.offset > 0 { + endpoint = fmt.Sprintf("%s&offset=%d", endpoint, tt.offset) + } + if tt.page > 0 { + endpoint = fmt.Sprintf("%s&page=%d", endpoint, tt.page) + } + req := httptest.NewRequest("GET", endpoint, nil) + req.Header.Add("content-type", "application/json") + req = req.WithContext(identity.WithRequester(req.Context(), &user.SignedInUser{Namespace: "test"})) + + searchHandler.DoSearch(rr, req) + + if mockClient.LastSearchRequest == nil { + t.Fatalf("expected Search to be called, but it was not") + } + + require.Equal(t, int(mockClient.LastSearchRequest.Offset), tt.expectedOffset, fmt.Sprintf("mismatch offset in test %d", i)) + require.Equal(t, int(mockClient.LastSearchRequest.Page), tt.expectedPage, fmt.Sprintf("mismatch page in test %d", i)) + } + }) +} + func TestSearchHandler(t *testing.T) { t.Run("Multiple comma separated fields will be appended to default dashboard search fields", func(t *testing.T) { // Create a mock client From 75923c0d2654afce0b95fb0444f558e7745e8316 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 21:02:59 +0000 Subject: [PATCH 62/75] Bump github.com/aws/smithy-go from 1.22.4 to 1.23.0 (#110374) Bumps [github.com/aws/smithy-go](https://github.com/aws/smithy-go) from 1.22.4 to 1.23.0. - [Release notes](https://github.com/aws/smithy-go/releases) - [Changelog](https://github.com/aws/smithy-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/smithy-go/compare/v1.22.4...v1.23.0) --- updated-dependencies: - dependency-name: github.com/aws/smithy-go dependency-version: 1.23.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- apps/iam/go.mod | 2 +- apps/iam/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- go.work.sum | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 792df436c8d..fb54ff9a38d 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -116,7 +116,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 // indirect - github.com/aws/smithy-go v1.22.5 // indirect + github.com/aws/smithy-go v1.23.1 // indirect github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index eb5066756c1..a75df64f1b1 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -281,8 +281,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 h1:pd9G9HQaM6UZAZh19pYOkpKS github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM= github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc= github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo= -github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= -github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= +github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f/go.mod h1:2stgcRjl6QmW+gU2h5E7BQXg4HU0gzxKWDuT5HviN9s= github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 h1:60m4tnanN1ctzIu4V3bfCNJ39BiOPSm1gHFlFjTkRE0= github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c= diff --git a/go.mod b/go.mod index e0e2073c0a8..5d49b559817 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 // @grafana/aws-datasources github.com/aws/aws-sdk-go-v2/service/oam v1.18.3 // @grafana/aws-datasources github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6 // @grafana/aws-datasources - github.com/aws/smithy-go v1.22.5 // @grafana/aws-datasources + github.com/aws/smithy-go v1.23.1 // @grafana/aws-datasources github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend github.com/blang/semver/v4 v4.0.0 // indirect; @grafana/grafana-developer-enablement-squad diff --git a/go.sum b/go.sum index 93feb28f947..0cc21ad631a 100644 --- a/go.sum +++ b/go.sum @@ -890,8 +890,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 h1:pd9G9HQaM6UZAZh19pYOkpKS github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM= github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc= github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo= -github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= -github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= +github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f/go.mod h1:2stgcRjl6QmW+gU2h5E7BQXg4HU0gzxKWDuT5HviN9s= github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 h1:60m4tnanN1ctzIu4V3bfCNJ39BiOPSm1gHFlFjTkRE0= github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c= diff --git a/go.work.sum b/go.work.sum index 229ea24d850..b4435727340 100644 --- a/go.work.sum +++ b/go.work.sum @@ -680,6 +680,7 @@ github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4 h1:hgSBvRT7JEWx2+vEGI9/Ld5rZtl7 github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4/go.mod h1:v7NIzEFIHBiicOMaMTuEmbnzGnqW0d+6ulNALul6fYE= github.com/aws/aws-sdk-go-v2/service/ssm v1.58.0 h1:zQz6Q5uaC8s9734DV9UDAm2q1TEEfOvEejDBSulOapI= github.com/aws/aws-sdk-go-v2/service/ssm v1.58.0/go.mod h1:PUWUl5MDiYNQkUHN9Pyd9kgtA/YhbxnSnHP+yQqzrM8= +github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/awslabs/aws-lambda-go-api-proxy v0.16.2 h1:CJyGEyO1CIwOnXTU40urf0mchf6t3voxpvUDikOU9LY= github.com/awslabs/aws-lambda-go-api-proxy v0.16.2/go.mod h1:vxxjwBHe/KbgFeNlAP/Tvp4SsVRL3WQamcWRxqVh0z0= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= From 85d9c84ebc60c9809f9bebf0fe49f3f2dd452ab7 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 00:39:00 +0000 Subject: [PATCH 63/75] I18n: Download translations from Crowdin (#112487) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 6 +++++- public/locales/de-DE/grafana.json | 6 +++++- public/locales/es-ES/grafana.json | 6 +++++- public/locales/fr-FR/grafana.json | 6 +++++- public/locales/hu-HU/grafana.json | 6 +++++- public/locales/id-ID/grafana.json | 6 +++++- public/locales/it-IT/grafana.json | 6 +++++- public/locales/ja-JP/grafana.json | 6 +++++- public/locales/ko-KR/grafana.json | 6 +++++- public/locales/nl-NL/grafana.json | 6 +++++- public/locales/pl-PL/grafana.json | 6 +++++- public/locales/pt-BR/grafana.json | 6 +++++- public/locales/pt-PT/grafana.json | 6 +++++- public/locales/ru-RU/grafana.json | 6 +++++- public/locales/sv-SE/grafana.json | 6 +++++- public/locales/tr-TR/grafana.json | 6 +++++- public/locales/zh-Hans/grafana.json | 6 +++++- public/locales/zh-Hant/grafana.json | 6 +++++- 18 files changed, 90 insertions(+), 18 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index eb4c859c056..dc3b6a0abe7 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -4869,7 +4869,11 @@ "add-visualization-header": "Začněte novou nástěnku přidáním vizualizace", "import-a-dashboard-body": "Importujte nástěnky ze souborů nebo z <2>grafana.com</2>.", "import-a-dashboard-header": "Importovat nástěnku", - "import-dashboard-button": "Importovat nástěnku" + "import-dashboard-button": "Importovat nástěnku", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Nástěnku se nepodařilo načíst" diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 41824924b7f..4032ecc1945 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -4829,7 +4829,11 @@ "add-visualization-header": "Starten Sie Ihr neues Dashboard, indem Sie eine Visualisierung hinzufügen", "import-a-dashboard-body": "Importieren Sie Dashboards aus Dateien oder von <2>grafana.com</2>.", "import-a-dashboard-header": "Dashboard importieren", - "import-dashboard-button": "Dashboard importieren" + "import-dashboard-button": "Dashboard importieren", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Dashboard konnte nicht geladen werden" diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 6fc2aa46aab..c03e7e1c845 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -4829,7 +4829,11 @@ "add-visualization-header": "Comienza tu nuevo panel de control añadiendo una visualización", "import-a-dashboard-body": "Importa dashboards desde archivos o <2>grafana.com</2>.", "import-a-dashboard-header": "Importar un tablero", - "import-dashboard-button": "Importar panel de control" + "import-dashboard-button": "Importar panel de control", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Error al cargar el panel de control" diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 7887b3526d1..ed78d341b34 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -4829,7 +4829,11 @@ "add-visualization-header": "Commencez votre nouveau tableau de bord en ajoutant une visualisation", "import-a-dashboard-body": "Importez des tableaux de bord à partir de fichiers ou du site <2>grafana.com</2>.", "import-a-dashboard-header": "Importer un tableau de bord", - "import-dashboard-button": "Importer un tableau de bord" + "import-dashboard-button": "Importer un tableau de bord", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Échec du chargement du tableau de bord" diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 9861ccc46c0..aae2b251622 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -4829,7 +4829,11 @@ "add-visualization-header": "Indítsa el az új irányítópultot vizualizáció hozzáadásával", "import-a-dashboard-body": "Irányítópultok importálása fájlokból vagy a <2>grafana.com</2> webhelyről.", "import-a-dashboard-header": "Irányítópult importálása", - "import-dashboard-button": "Irányítópult importálása" + "import-dashboard-button": "Irányítópult importálása", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Az irányítópult betöltése sikertelen volt" diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 0b15556e001..eb62a60b773 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -4809,7 +4809,11 @@ "add-visualization-header": "Mulai dasbor baru Anda dengan menambahkan visualisasi", "import-a-dashboard-body": "Impor dasbor dari file atau <2>grafana.com</2>.", "import-a-dashboard-header": "Impor dasbor", - "import-dashboard-button": "Impor dasbor" + "import-dashboard-button": "Impor dasbor", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Gagal memuat dasbor" diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 98f52e220f6..37040914569 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -4829,7 +4829,11 @@ "add-visualization-header": "Avvia il tuo nuovo dashboard aggiungendo una visualizzazione", "import-a-dashboard-body": "Importa dashboard da file o <2>grafana.com</2>.", "import-a-dashboard-header": "Importa un dashboard", - "import-dashboard-button": "Importa dashboard" + "import-dashboard-button": "Importa dashboard", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Impossibile caricare il dashboard" diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 8b229b31c6c..caf74479696 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -4809,7 +4809,11 @@ "add-visualization-header": "視覚化を追加して新しいダッシュボードを開始します", "import-a-dashboard-body": "ファイルまたは<1>grafana.com</1>からダッシュボードをインポートします。", "import-a-dashboard-header": "ダッシュボードをインポートする", - "import-dashboard-button": "ダッシュボードをインポート" + "import-dashboard-button": "ダッシュボードをインポート", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "ダッシュボードの読み込みに失敗しました" diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index dbddbd32f19..b7ff37ac408 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -4809,7 +4809,11 @@ "add-visualization-header": "시각화 추가하여 새 대시보드 시작하기", "import-a-dashboard-body": "파일 또는 <1>grafana.com</1>에서 대시보드 가져오기", "import-a-dashboard-header": "대시보드 가져오기", - "import-dashboard-button": "대시보드 가져오기" + "import-dashboard-button": "대시보드 가져오기", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "대시보드 로딩에 실패했습니다." diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index db8524a5e52..88f0c436ad9 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -4829,7 +4829,11 @@ "add-visualization-header": "Begin je nieuwe dashboard door een visualisatie toe te voegen", "import-a-dashboard-body": "Importeer dashboards uit bestanden of <1>grafana.com</1>.", "import-a-dashboard-header": "Dashboard importeren", - "import-dashboard-button": "Dashboard importeren" + "import-dashboard-button": "Dashboard importeren", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Kan dashboard niet laden" diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 885c9b4b1e0..d29d24151a7 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -4869,7 +4869,11 @@ "add-visualization-header": "Dodaj wizualizację, aby rozpocząć tworzenie nowego pulpitu", "import-a-dashboard-body": "Zaimportuj pulpity z plików lub witryny <2>grafana.com</2>.", "import-a-dashboard-header": "Importuj pulpit", - "import-dashboard-button": "Importuj pulpit" + "import-dashboard-button": "Importuj pulpit", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Nie udało się załadować pulpitu" diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 83ea25c2b1d..549a097b39e 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -4829,7 +4829,11 @@ "add-visualization-header": "Comece seu novo painel de controle adicionando uma visualização", "import-a-dashboard-body": "Importe painéis por meio de arquivos ou do <2>grafana.com</2>.", "import-a-dashboard-header": "Importar um painel de controle", - "import-dashboard-button": "Importar painel de controle" + "import-dashboard-button": "Importar painel de controle", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Falha ao carregar o painel de controle" diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index c7841f5ac8d..5107956ec5a 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -4829,7 +4829,11 @@ "add-visualization-header": "Inicie o seu novo painel de controlo ao adicionar uma visualização", "import-a-dashboard-body": "Importe painéis de controlo de ficheiros ou de <2>grafana.com</2>.", "import-a-dashboard-header": "Importar um painel de controlo", - "import-dashboard-button": "Importar painel de controlo" + "import-dashboard-button": "Importar painel de controlo", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Não foi possível carregar o painel de controlo" diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 817cd48e9fb..9300f426b0e 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -4869,7 +4869,11 @@ "add-visualization-header": "Создание нового дашборда с добавлением визуализации", "import-a-dashboard-body": "Импортируйте дашборды из файлов или с <1>grafana.com</1>.", "import-a-dashboard-header": "Импорт дашборда", - "import-dashboard-button": "Импорт дашборда" + "import-dashboard-button": "Импорт дашборда", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Не удалось загрузить дашборд" diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 0a9891de27d..07926ee5e53 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -4829,7 +4829,11 @@ "add-visualization-header": "Starta din nya instrumentpanel genom att lägga till en visualisering", "import-a-dashboard-body": "Importera instrumentpaneler från filer eller från <2>grafana.com</2>.", "import-a-dashboard-header": "Importera en instrumentpanel", - "import-dashboard-button": "Importera instrumentpanel" + "import-dashboard-button": "Importera instrumentpanel", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Det gick inte att ladda instrumentpanelen" diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 14e8e592605..39552662f20 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -4829,7 +4829,11 @@ "add-visualization-header": "Bir görselleştirme ekleyerek yeni panonuzu başlatın", "import-a-dashboard-body": "", "import-a-dashboard-header": "Bir panoyu içe aktar", - "import-dashboard-button": "Panoyu içe aktar" + "import-dashboard-button": "Panoyu içe aktar", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "Pano yüklenemedi" diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index a8aad548310..6b619723fb2 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -4809,7 +4809,11 @@ "add-visualization-header": "通过添加可视化开始您的新仪表板", "import-a-dashboard-body": "从文件或 <2>grafana.com</2> 导入数据面板。", "import-a-dashboard-header": "导入仪表板", - "import-dashboard-button": "导入仪表板" + "import-dashboard-button": "导入仪表板", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "加载数据面板失败" diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index b35477c68bf..58306447fbb 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -4809,7 +4809,11 @@ "add-visualization-header": "透過新增可視化來啟動您的新儀表板", "import-a-dashboard-body": "從檔案或 <2>grafana.com</2> 匯入儀表板。", "import-a-dashboard-header": "匯入儀表板", - "import-dashboard-button": "匯入儀表板" + "import-dashboard-button": "匯入儀表板", + "show-less-dashboards": "", + "show-more-dashboards": "", + "start-with-suggested-dashboards": "", + "use-template-button": "" }, "errors": { "failed-to-load": "無法載入儀表板" From a9dc2994e10f8862c8fa7d7cd3780a39192cdd9b Mon Sep 17 00:00:00 2001 From: Konrad Lalik <konradlalik@gmail.com> Date: Thu, 16 Oct 2025 08:06:14 +0200 Subject: [PATCH 64/75] Alerting: Add position-based matching for identical alert rules (#112407) * Add rule index as a part of the rule matching process * Refactor rule matching to use a dedicated RulePositionHash type - Introduced a new `rulePositionHash.ts` file to define a branded type for rule positions. - Updated rule matching logic to utilize the new `createRulePositionHash` function for generating rule position hashes. - Adjusted related components and tests to ensure consistent handling of rule indices and positions. --- .../rule-list/DataSourceGroupLoader.tsx | 61 +++--- .../rule-list/DataSourceRuleLoader.tsx | 16 +- .../alerting/unified/rule-list/FilterView.tsx | 6 +- .../hooks/useFilteredRulesIterator.ts | 25 ++- .../unified/rule-list/ruleMatching.test.ts | 207 +++++++++++++++++- .../unified/rule-list/ruleMatching.ts | 74 ++++++- .../unified/rule-list/rulePositionHash.ts | 20 ++ 7 files changed, 350 insertions(+), 59 deletions(-) create mode 100644 public/app/features/alerting/unified/rule-list/rulePositionHash.ts diff --git a/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.tsx b/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.tsx index bd221cd72ed..ad1e4e8d87e 100644 --- a/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.tsx +++ b/public/app/features/alerting/unified/rule-list/DataSourceGroupLoader.tsx @@ -191,24 +191,29 @@ export function RulerBasedGroupRules({ return ( <> - {pageItems.map((rulerRule) => { + {pageItems.map((rulerRule, index) => { + // If rules are indistinguishable by name, labels, annotations, and query, we need to use the index to disambiguate const promRule = matches.get(rulerRule); - return promRule ? ( - <DataSourceRuleListItem - key={hashRule(promRule)} - rule={promRule} - rulerRule={rulerRule} - groupIdentifier={groupIdentifier} - application={application} - actions={ - <RuleActionsButtons rule={rulerRule} promRule={promRule} groupIdentifier={groupIdentifier} compact /> - } - showLocation={false} - /> - ) : ( + if (promRule) { + return ( + <DataSourceRuleListItem + key={`${hashRule(promRule)}-${index}`} + rule={promRule} + rulerRule={rulerRule} + groupIdentifier={groupIdentifier} + application={application} + actions={ + <RuleActionsButtons rule={rulerRule} promRule={promRule} groupIdentifier={groupIdentifier} compact /> + } + showLocation={false} + /> + ); + } + + return ( <RuleOperationListItem - key={getRuleName(rulerRule)} + key={`${getRuleName(rulerRule)}-${index}`} name={getRuleName(rulerRule)} namespace={namespace.name} group={groupName} @@ -219,18 +224,20 @@ export function RulerBasedGroupRules({ /> ); })} - {promOnlyRules.map((rule) => ( - <RuleOperationListItem - key={rule.name} - name={rule.name} - namespace={namespace.name} - group={groupName} - rulesSource={groupIdentifier.rulesSource} - application={application} - operation="deleting" - showLocation={false} - /> - ))} + {promOnlyRules.map((rule, index) => { + return ( + <RuleOperationListItem + key={`${rule.name}-${index}`} + name={rule.name} + namespace={namespace.name} + group={groupName} + rulesSource={groupIdentifier.rulesSource} + application={application} + operation="deleting" + showLocation={false} + /> + ); + })} {hasMore && ( <li aria-selected="false" role="treeitem" className={styles.loadMoreWrapper}> <LoadMoreButton onClick={loadMore} /> diff --git a/public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx b/public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx index ac54bc375b3..5e0cf04d982 100644 --- a/public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx +++ b/public/app/features/alerting/unified/rule-list/DataSourceRuleLoader.tsx @@ -1,8 +1,6 @@ import { skipToken } from '@reduxjs/toolkit/query'; import { memo, useMemo } from 'react'; -import { DataSourceRuleGroupIdentifier, Rule } from 'app/types/unified-alerting'; - import { alertRuleApi } from '../api/alertRuleApi'; import { featureDiscoveryApi } from '../api/featureDiscoveryApi'; import { isCloudRulerGroup } from '../utils/rules'; @@ -10,20 +8,18 @@ import { isCloudRulerGroup } from '../utils/rules'; import { DataSourceRuleListItem } from './DataSourceRuleListItem'; import { RuleActionsButtons } from './components/RuleActionsButtons.V2'; import { RuleActionsSkeleton } from './components/RuleActionsSkeleton'; +import { PromRuleWithOrigin } from './hooks/useFilteredRulesIterator'; import { getMatchingRulerRule } from './ruleMatching'; const { useDiscoverDsFeaturesQuery } = featureDiscoveryApi; const { useGetRuleGroupForNamespaceQuery } = alertRuleApi; interface DataSourceRuleLoaderProps { - rule: Rule; - groupIdentifier: DataSourceRuleGroupIdentifier; + ruleWithOrigin: PromRuleWithOrigin; } -export const DataSourceRuleLoader = memo(function DataSourceRuleLoader({ - rule, - groupIdentifier, -}: DataSourceRuleLoaderProps) { +export const DataSourceRuleLoader = memo(function DataSourceRuleLoader({ ruleWithOrigin }: DataSourceRuleLoaderProps) { + const { rule, groupIdentifier } = ruleWithOrigin; const { rulesSource, namespace, groupName } = groupIdentifier; const { data: dsFeatures } = useDiscoverDsFeaturesQuery({ uid: rulesSource.uid }); @@ -43,8 +39,8 @@ export const DataSourceRuleLoader = memo(function DataSourceRuleLoader({ return; } - return getMatchingRulerRule(rulerRuleGroup, rule); - }, [rulerRuleGroup, rule]); + return getMatchingRulerRule(rulerRuleGroup, ruleWithOrigin); + }, [rulerRuleGroup, ruleWithOrigin]); // 1. get the rule from the ruler API with "ruleWithLocation" // 1.1 skip this if this datasource does not have a ruler diff --git a/public/app/features/alerting/unified/rule-list/FilterView.tsx b/public/app/features/alerting/unified/rule-list/FilterView.tsx index 08a8ecbce43..5e1a651cdaf 100644 --- a/public/app/features/alerting/unified/rule-list/FilterView.tsx +++ b/public/app/features/alerting/unified/rule-list/FilterView.tsx @@ -162,7 +162,7 @@ function FilterViewResults({ filterState }: FilterViewProps) { /> ); case 'datasource': - return <DataSourceRuleLoader key={key} rule={rule} groupIdentifier={groupIdentifier} />; + return <DataSourceRuleLoader key={key} ruleWithOrigin={ruleWithOrigin} />; default: return ( <UnknownRuleListItem @@ -212,7 +212,9 @@ function getGrafanaRuleKey(ruleWithOrigin: GrafanaRuleWithOrigin) { function getDataSourceRuleKey(ruleWithOrigin: PromRuleWithOrigin) { const { rule, + rulePositionHash, groupIdentifier: { rulesSource, namespace, groupName }, } = ruleWithOrigin; - return `${rulesSource.name}-${namespace.name}-${groupName}-${rule.name}-${rule.type}-${hashRule(rule)}`; + + return `${rulesSource.name}-${namespace.name}-${groupName}-${rule.name}-${rule.type}-${hashRule(rule)}-${rulePositionHash}`; } diff --git a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts index 4139e4e2a62..b1611558604 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/useFilteredRulesIterator.ts @@ -22,6 +22,7 @@ import { getExternalRulesSources, isSupportedExternalRulesSourceType, } from '../../utils/datasource'; +import { RulePositionHash, createRulePositionHash } from '../rulePositionHash'; import { groupFilter, ruleFilter } from './filters'; import { useGrafanaGroupsGenerator, usePrometheusGroupsGenerator } from './prometheusGroupsGenerator'; @@ -43,6 +44,22 @@ export interface PromRuleWithOrigin { rule: PromRuleDTO; groupIdentifier: DataSourceRuleGroupIdentifier; origin: 'datasource'; + /** + * Position hash encoding both the rule's index and the total number of rules in the group. + * Format: "<index>:<totalRules>" (e.g., "0:3", "1:5") + * + * This is used as a tiebreaker when multiple identical rules exist in a group and need to be + * matched against their counterparts in another rule source (e.g., matching Prometheus rules + * to Ruler API rules). The hash ensures that identical rules are matched by their position + * only when both groups have the same structure (same number of rules). + * + * @example + * // Two identical alerts in different positions + * Rule at position 0 in a 3-rule group: rulePositionHash = "0:3" + * Rule at position 1 in a 3-rule group: rulePositionHash = "1:3" + * // These won't match rules in a 2-rule group (e.g., "0:2") even if identical + */ + rulePositionHash: RulePositionHash; } interface GetIteratorResult { @@ -99,9 +116,9 @@ export function useFilteredRulesIteratorProvider() { concatMap((groups) => groups .filter((group) => groupFilter(group, normalizedFilterState)) - .flatMap((group) => group.rules.map((rule) => ({ group, rule }))) + .flatMap((group) => group.rules.map((rule, index) => ({ group, rule, index }))) .filter(({ rule }) => ruleFilter(rule, normalizedFilterState)) - .map(({ group, rule }) => mapRuleToRuleWithOrigin(dataSourceIdentifier, group, rule)) + .map(({ group, rule, index }) => mapRuleToRuleWithOrigin(dataSourceIdentifier, group, rule, index)) ), catchError(() => empty()) ); @@ -166,7 +183,8 @@ function getRulesSourcesFromFilter(filter: RulesFilter): DataSourceRulesSourceId function mapRuleToRuleWithOrigin( rulesSource: DataSourceRulesSourceIdentifier, group: PromRuleGroupDTO, - rule: PromRuleDTO + rule: PromRuleDTO, + ruleIndex: number ): PromRuleWithOrigin { return { rule, @@ -177,6 +195,7 @@ function mapRuleToRuleWithOrigin( groupOrigin: 'datasource', }, origin: 'datasource', + rulePositionHash: createRulePositionHash(ruleIndex, group.rules.length), }; } diff --git a/public/app/features/alerting/unified/rule-list/ruleMatching.test.ts b/public/app/features/alerting/unified/rule-list/ruleMatching.test.ts index ee542bac1bf..87ee3e35f95 100644 --- a/public/app/features/alerting/unified/rule-list/ruleMatching.test.ts +++ b/public/app/features/alerting/unified/rule-list/ruleMatching.test.ts @@ -1,7 +1,38 @@ +import { PromRuleDTO, RulerCloudRuleDTO } from 'app/types/unified-alerting-dto'; + import { mockPromRecordingRule } from '../mocks'; import { alertingFactory } from '../mocks/server/db'; +import { PromRuleWithOrigin } from './hooks/useFilteredRulesIterator'; import { getMatchingPromRule, getMatchingRulerRule, matchRulesGroup } from './ruleMatching'; +import { RulePositionHash, createRulePositionHash } from './rulePositionHash'; + +// Helper to create PromRuleWithOrigin mock +function createPromRuleWithOrigin(rule: PromRuleDTO, ruleIndex: number, totalRules: number): PromRuleWithOrigin { + return { + rule, + groupIdentifier: { + rulesSource: { uid: 'test-ds', name: 'test-datasource', ruleSourceType: 'datasource' }, + namespace: { name: 'test-namespace' }, + groupName: 'test-group', + groupOrigin: 'datasource', + }, + origin: 'datasource', + rulePositionHash: createRulePositionHash(ruleIndex, totalRules), + }; +} + +// Helper to create RulerRuleWithRulePosition mock +function createRulerRuleWithPosition( + rule: RulerCloudRuleDTO, + ruleIndex: number, + totalRules: number +): RulerCloudRuleDTO & { rulePositionHash: RulePositionHash } { + return { + ...rule, + rulePositionHash: createRulePositionHash(ruleIndex, totalRules), + }; +} describe('getMatchingRulerRule', () => { it('should match rule by unique name', () => { @@ -11,8 +42,9 @@ describe('getMatchingRulerRule', () => { // Create a matching prom rule with same name const promRule = alertingFactory.prometheus.rule.build({ name: 'test-rule' }); + const promRuleWithOrigin = createPromRuleWithOrigin(promRule, 0, 1); - const match = getMatchingRulerRule(rulerGroup, promRule); + const match = getMatchingRulerRule(rulerGroup, promRuleWithOrigin); expect(match).toBe(rulerRule); }); @@ -30,8 +62,9 @@ describe('getMatchingRulerRule', () => { labels: { severity: 'warning' }, annotations: { summary: 'test' }, }); + const promRuleWithOrigin = createPromRuleWithOrigin(promRule, 0, 1); - const match = getMatchingRulerRule(rulerGroup, promRule); + const match = getMatchingRulerRule(rulerGroup, promRuleWithOrigin); expect(match).toBeUndefined(); }); @@ -55,8 +88,9 @@ describe('getMatchingRulerRule', () => { labels: { severity: 'warning' }, annotations: { summary: 'test' }, }); + const promRuleWithOrigin = createPromRuleWithOrigin(promRule, 0, 2); - const match = getMatchingRulerRule(rulerGroup, promRule); + const match = getMatchingRulerRule(rulerGroup, promRuleWithOrigin); expect(match).toBe(rulerRule1); }); @@ -83,8 +117,9 @@ describe('getMatchingRulerRule', () => { annotations: { summary: 'test' }, query: 'up == 1', }); + const promRuleWithOrigin = createPromRuleWithOrigin(promRule, 0, 2); - const match = getMatchingRulerRule(rulerGroup, promRule); + const match = getMatchingRulerRule(rulerGroup, promRuleWithOrigin); expect(match).toBe(rulerRule1); }); @@ -111,8 +146,9 @@ describe('getMatchingRulerRule', () => { annotations: { summary: 'other' }, query: 'up == 2', }); + const promRuleWithOrigin = createPromRuleWithOrigin(promRule, 0, 2); - const match = getMatchingRulerRule(rulerGroup, promRule); + const match = getMatchingRulerRule(rulerGroup, promRuleWithOrigin); expect(match).toBeUndefined(); }); @@ -141,10 +177,120 @@ max by (service) (up{env="staging"}) * 0.8`, labels: {}, query: `max by (service) (up{env="production"}) or max by (service) (up{env="staging"}) * 0.8`, }); + const promRuleWithOrigin = createPromRuleWithOrigin(promRule, 0, 2); - const match = getMatchingRulerRule(rulerGroup, promRule); + const match = getMatchingRulerRule(rulerGroup, promRuleWithOrigin); expect(match).toBe(rulerRuleWithComments); }); + + it('should match prometheus rule against one of two identical ruler rules', () => { + // Create two identical ruler alerting rules + const rulerRule1 = alertingFactory.ruler.alertingRule.build({ + alert: 'KubeJobFailed', + expr: `kube_job_failed{job!=\"\"} > 0\n`, + labels: { severity: 'warning' }, + annotations: { summary: 'Job failed to complete.' }, + }); + const rulerRule2 = alertingFactory.ruler.alertingRule.build({ + alert: 'KubeJobFailed', + expr: `kube_job_failed{job!=\"\"} > 0\n`, + labels: { severity: 'warning' }, + annotations: { summary: 'Job failed to complete.' }, + }); + const rulerGroup = alertingFactory.ruler.group.build({ rules: [rulerRule1, rulerRule2] }); + + // Create corresponding prometheus rule at position 0 (should match rulerRule1) + const promRule = alertingFactory.prometheus.rule.build({ + name: 'KubeJobFailed', + query: `kube_job_failed{job!=\"\"} > 0`, + labels: { severity: 'warning' }, + annotations: { summary: 'Job failed to complete.' }, + }); + const promRuleWithOrigin = createPromRuleWithOrigin(promRule, 0, 2); + + const match = getMatchingRulerRule(rulerGroup, promRuleWithOrigin); + // Should match the first ruler rule because position hash matches (0:2) + expect(match).toBeDefined(); + expect(match).toEqual(rulerRule1); + }); + + it('should match second identical rule when position hash indicates index 1', () => { + // Create three identical ruler alerting rules to test matching at different positions + const rulerRule1 = alertingFactory.ruler.alertingRule.build({ + alert: 'HighMemoryUsage', + expr: `memory_usage > 90`, + labels: { severity: 'critical' }, + annotations: { summary: 'High memory' }, + }); + const rulerRule2 = alertingFactory.ruler.alertingRule.build({ + alert: 'HighMemoryUsage', + expr: `memory_usage > 90`, + labels: { severity: 'critical' }, + annotations: { summary: 'High memory' }, + }); + const rulerRule3 = alertingFactory.ruler.alertingRule.build({ + alert: 'HighMemoryUsage', + expr: `memory_usage > 90`, + labels: { severity: 'critical' }, + annotations: { summary: 'High memory' }, + }); + const rulerGroup = alertingFactory.ruler.group.build({ rules: [rulerRule1, rulerRule2, rulerRule3] }); + + // Create prometheus rule at position 1 (should match rulerRule2, the middle one) + const promRule = alertingFactory.prometheus.rule.build({ + name: 'HighMemoryUsage', + query: `memory_usage > 90`, + labels: { severity: 'critical' }, + annotations: { summary: 'High memory' }, + }); + const promRuleWithOrigin = createPromRuleWithOrigin(promRule, 1, 3); + + const match = getMatchingRulerRule(rulerGroup, promRuleWithOrigin); + // Should match the second ruler rule because position hash is "1:3" + expect(match).toBeDefined(); + expect(match).toBe(rulerRule2); + // Verify it's NOT matching the first or third rule + expect(match).not.toBe(rulerRule1); + expect(match).not.toBe(rulerRule3); + }); + + it('should NOT match when group sizes differ even with identical rules', () => { + // Create a ruler group with 3 identical rules + const rulerRule1 = alertingFactory.ruler.alertingRule.build({ + alert: 'DiskSpaceLow', + expr: `disk_free < 10`, + labels: { severity: 'warning' }, + annotations: { summary: 'Low disk space' }, + }); + const rulerRule2 = alertingFactory.ruler.alertingRule.build({ + alert: 'DiskSpaceLow', + expr: `disk_free < 10`, + labels: { severity: 'warning' }, + annotations: { summary: 'Low disk space' }, + }); + const rulerRule3 = alertingFactory.ruler.alertingRule.build({ + alert: 'DiskSpaceLow', + expr: `disk_free < 10`, + labels: { severity: 'warning' }, + annotations: { summary: 'Low disk space' }, + }); + const rulerGroup = alertingFactory.ruler.group.build({ rules: [rulerRule1, rulerRule2, rulerRule3] }); + + // Create prometheus rule with position hash indicating 2 rules total (but ruler has 3) + const promRule = alertingFactory.prometheus.rule.build({ + name: 'DiskSpaceLow', + query: `disk_free < 10`, + labels: { severity: 'warning' }, + annotations: { summary: 'Low disk space' }, + }); + // Position hash "0:2" indicates prom group has 2 rules, but ruler group has 3 + const promRuleWithOrigin = createPromRuleWithOrigin(promRule, 0, 2); + + const match = getMatchingRulerRule(rulerGroup, promRuleWithOrigin); + // Should NOT match because position hash "0:2" doesn't match any ruler rule + // (ruler rules would have hashes "0:3", "1:3", "2:3") + expect(match).toBeUndefined(); + }); }); describe('getMatchingPromRule', () => { @@ -155,8 +301,9 @@ describe('getMatchingPromRule', () => { // Create a matching ruler rule with same name const rulerRule = alertingFactory.ruler.alertingRule.build({ alert: 'test-rule' }); + const rulerRuleWithPosition = createRulerRuleWithPosition(rulerRule, 0, 1); - const match = getMatchingPromRule(promGroup, rulerRule); + const match = getMatchingPromRule(promGroup, rulerRuleWithPosition); expect(match).toBe(promRule); }); @@ -174,8 +321,9 @@ describe('getMatchingPromRule', () => { labels: { severity: 'warning' }, annotations: { summary: 'test' }, }); + const rulerRuleWithPosition = createRulerRuleWithPosition(rulerRule, 0, 1); - const match = getMatchingPromRule(promGroup, rulerRule); + const match = getMatchingPromRule(promGroup, rulerRuleWithPosition); expect(match).toBeUndefined(); }); @@ -199,8 +347,9 @@ describe('getMatchingPromRule', () => { labels: { severity: 'warning' }, annotations: { summary: 'test' }, }); + const rulerRuleWithPosition = createRulerRuleWithPosition(rulerRule, 0, 2); - const match = getMatchingPromRule(promGroup, rulerRule); + const match = getMatchingPromRule(promGroup, rulerRuleWithPosition); expect(match).toBe(promRule1); }); @@ -227,8 +376,9 @@ describe('getMatchingPromRule', () => { annotations: { summary: 'test' }, expr: 'up == 1', }); + const rulerRuleWithPosition = createRulerRuleWithPosition(rulerRule, 0, 2); - const match = getMatchingPromRule(promGroup, rulerRule); + const match = getMatchingPromRule(promGroup, rulerRuleWithPosition); expect(match).toBe(promRule1); }); @@ -255,8 +405,9 @@ describe('getMatchingPromRule', () => { annotations: { summary: 'other' }, expr: 'up == 2', }); + const rulerRuleWithPosition = createRulerRuleWithPosition(rulerRule, 0, 2); - const match = getMatchingPromRule(promGroup, rulerRule); + const match = getMatchingPromRule(promGroup, rulerRuleWithPosition); expect(match).toBeUndefined(); }); @@ -285,10 +436,42 @@ or # Fall back to staging for missing production metrics max by (service) (up{env="staging"}) * 0.8`, }); + const rulerRuleWithPosition = createRulerRuleWithPosition(rulerRule, 0, 2); - const match = getMatchingPromRule(promGroup, rulerRule); + const match = getMatchingPromRule(promGroup, rulerRuleWithPosition); expect(match).toBe(promRuleWithoutComments); }); + + it('should match ruler rule against one of two identical prometheus rules', () => { + // Create two identical prometheus alerting rules + const promRule1 = alertingFactory.prometheus.rule.build({ + name: 'KubeJobFailed', + query: `kube_job_failed{job!=\"\"} > 0`, + labels: { severity: 'warning' }, + annotations: { summary: 'Job failed to complete.' }, + }); + const promRule2 = alertingFactory.prometheus.rule.build({ + name: 'KubeJobFailed', + query: `kube_job_failed{job!=\"\"} > 0`, + labels: { severity: 'warning' }, + annotations: { summary: 'Job failed to complete.' }, + }); + const promGroup = alertingFactory.prometheus.group.build({ rules: [promRule1, promRule2] }); + + // Create corresponding ruler rule at position 0 (should match promRule1) + const rulerRule = alertingFactory.ruler.alertingRule.build({ + alert: 'KubeJobFailed', + expr: `kube_job_failed{job!=\"\"} > 0\n`, + labels: { severity: 'warning' }, + annotations: { summary: 'Job failed to complete.' }, + }); + const rulerRuleWithPosition = createRulerRuleWithPosition(rulerRule, 0, 2); + + const match = getMatchingPromRule(promGroup, rulerRuleWithPosition); + // Should match the first prometheus rule because position hash matches (0:2) + expect(match).toBeDefined(); + expect(match).toEqual(promRule1); + }); }); describe('matchRulesGroup', () => { diff --git a/public/app/features/alerting/unified/rule-list/ruleMatching.ts b/public/app/features/alerting/unified/rule-list/ruleMatching.ts index 513a43be6f8..ad188ac2526 100644 --- a/public/app/features/alerting/unified/rule-list/ruleMatching.ts +++ b/public/app/features/alerting/unified/rule-list/ruleMatching.ts @@ -1,4 +1,3 @@ -import { Rule } from 'app/types/unified-alerting'; import { PromRuleDTO, PromRuleGroupDTO, @@ -10,7 +9,15 @@ import { import { getPromRuleFingerprint, getRulerRuleFingerprint } from '../utils/rule-id'; import { getRuleName } from '../utils/rules'; -export function getMatchingRulerRule(rulerRuleGroup: RulerRuleGroupDTO<RulerCloudRuleDTO>, rule: Rule) { +import { PromRuleWithOrigin } from './hooks/useFilteredRulesIterator'; +import { RulePositionHash, createRulePositionHash } from './rulePositionHash'; + +export function getMatchingRulerRule( + rulerRuleGroup: RulerRuleGroupDTO<RulerCloudRuleDTO>, + promRuleWithOrigin: PromRuleWithOrigin +) { + const { rule, rulePositionHash } = promRuleWithOrigin; + // If all rule names are unique, we can use the rule name to find the rule. We don't need to hash the rule const rulesByName = rulerRuleGroup.rules.filter((r) => getRuleName(r) === rule.name); if (rulesByName.length === 1) { @@ -35,10 +42,47 @@ export function getMatchingRulerRule(rulerRuleGroup: RulerRuleGroupDTO<RulerClou return rulesByLabelsAndAnnotationsAndQuery[0]; } + // If there are still multiple identical matches, use position hash to disambiguate + // This only works if both groups have the same structure (same number of rules in the same order) + if (rulesByLabelsAndAnnotationsAndQuery.length > 1 && rulePositionHash) { + for (const candidateRule of rulesByLabelsAndAnnotationsAndQuery) { + const rulerRuleIndex = rulerRuleGroup.rules.indexOf(candidateRule); + const rulerPositionHash = createRulePositionHash(rulerRuleIndex, rulerRuleGroup.rules.length); + + // Match if position hashes are identical + if (rulerPositionHash === rulePositionHash) { + return candidateRule; + } + } + } + return undefined; } -export function getMatchingPromRule(promRuleGroup: PromRuleGroupDTO<PromRuleDTO>, rule: RulerCloudRuleDTO) { +type RulerRuleWithRulePosition = RulerCloudRuleDTO & { + /** + * Position hash encoding both the rule's index and the total number of rules in the group. + * Format: "<index>:<totalRules>" (e.g., "0:3", "1:5") + * + * This is used to disambiguate between identical Ruler rules when matching them against + * Prometheus rules. The hash ensures rules are matched by position only when both groups + * have the same structure (same number of rules). + * + * @example + * // Matching identical rules by position + * Ruler rule at position 0 in a 2-rule group: rulePositionHash = "0:2" + * Prometheus rule at position 0 in a 2-rule group: rulePositionHash = "0:2" → Match! + * Prometheus rule at position 0 in a 3-rule group: rulePositionHash = "0:3" → No match + */ + rulePositionHash: RulePositionHash; +}; + +export function getMatchingPromRule( + promRuleGroup: PromRuleGroupDTO<PromRuleDTO>, + rulerRuleWithPosition: RulerRuleWithRulePosition +) { + const { rulePositionHash, ...rule } = rulerRuleWithPosition; + // If all rule names are unique, we can use the rule name to find the rule. We don't need to hash the rule const rulesByName = promRuleGroup.rules.filter((r) => r.name === getRuleName(rule)); if (rulesByName.length === 1) { @@ -63,6 +107,20 @@ export function getMatchingPromRule(promRuleGroup: PromRuleGroupDTO<PromRuleDTO> return rulesByLabelsAndAnnotationsAndQuery[0]; } + // If there are still multiple identical matches, use position hash to disambiguate + // This only works if both groups have the same structure (same number of rules in the same order) + if (rulesByLabelsAndAnnotationsAndQuery.length > 1 && rulePositionHash) { + for (const candidateRule of rulesByLabelsAndAnnotationsAndQuery) { + const promRuleIndex = promRuleGroup.rules.indexOf(candidateRule); + const promPositionHash = createRulePositionHash(promRuleIndex, promRuleGroup.rules.length); + + // Match if position hashes are identical + if (promPositionHash === rulePositionHash) { + return candidateRule; + } + } + } + return undefined; } @@ -76,10 +134,16 @@ export function matchRulesGroup( promGroup: PromRuleGroupDTO<PromRuleDTO> ): GroupMatchingResult { const matchingResult = rulerGroup.rules.reduce( - (acc, rulerRule) => { + (acc, rulerRule, index) => { const { matches, unmatchedPromRules } = acc; - const promRule = getMatchingPromRule(promGroup, rulerRule); + // Create ruler rule with position hash + const rulerRuleWithPosition: RulerRuleWithRulePosition = { + ...rulerRule, + rulePositionHash: createRulePositionHash(index, rulerGroup.rules.length), + }; + + const promRule = getMatchingPromRule(promGroup, rulerRuleWithPosition); if (promRule) { matches.set(rulerRule, promRule); unmatchedPromRules.delete(promRule); diff --git a/public/app/features/alerting/unified/rule-list/rulePositionHash.ts b/public/app/features/alerting/unified/rule-list/rulePositionHash.ts new file mode 100644 index 00000000000..462cc415f31 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/rulePositionHash.ts @@ -0,0 +1,20 @@ +/** + * A branded type representing a rule's position within a group. + * Format: "<index>:<totalRules>" (e.g., "0:3", "1:5") + * + * This is used to disambiguate between identical rules when matching them across + * different rule sources (e.g., matching Prometheus rules to Ruler API rules). + * The hash ensures rules are matched by position only when both groups have the + * same structure (same number of rules). + * + * @example + * // Matching identical rules by position + * Rule at position 0 in a 2-rule group: "0:2" + * Rule at position 1 in a 3-rule group: "1:3" + * // These won't match rules in differently-sized groups even if identical + */ +export type RulePositionHash = `${number}:${number}`; + +export function createRulePositionHash(ruleIndex: number, totalRules: number): RulePositionHash { + return `${ruleIndex}:${totalRules}`; +} From bd956597592f46f5c487029dc46c5d7143ec7498 Mon Sep 17 00:00:00 2001 From: Alex Khomenko <Clarity-89@users.noreply.github.com> Date: Thu, 16 Oct 2025 09:22:03 +0300 Subject: [PATCH 65/75] Chore: Add noMargin prop to Card components (#110458) * Chore: Add noMargin to Card components * Update results * Update ConfigureCorrelationSourceForm.tsx * Update AlertsFolderView.tsx * Update VariableSetEditableElement.tsx * Update DataSourceCard.tsx * Update data source lists * Move betterer rule to eslint * Update playlists * Update query patterns * Update AzureCheatSheet.tsx * betterer * Update NavLandingPageCard.tsx * Update ServerStatsCard.tsx * Cleanup * Remove Field rule * cleanup * Update suppressions * upd * Merge * lint * Update public dashboards list --- eslint-suppressions.json | 125 +--------------- .../src/querybuilder/QueryPattern.tsx | 2 +- .../src/querybuilder/QueryPatternsModal.tsx | 12 +- .../AppChrome/History/HistoryWrapper.tsx | 4 +- .../StoredNotificationItem.tsx | 2 +- .../NavLandingPage/NavLandingPageCard.tsx | 3 +- public/app/features/admin/ServerStatsCard.tsx | 5 +- .../alerting/unified/AlertsFolderView.tsx | 4 +- .../alerting/unified/RedirectToRuleViewer.tsx | 2 +- .../receivers/form/GenerateAlertDataModal.tsx | 2 +- .../components/rule-editor/QueryRows.tsx | 2 +- .../rule-editor/rule-types/RuleType.tsx | 2 +- .../components/settings/AlertmanagerCard.tsx | 2 +- .../unified/rule-list/FilterViewStatus.tsx | 2 +- .../tabs/ConnectData/CardGrid/CardGrid.tsx | 3 +- .../Forms/ConfigureCorrelationSourceForm.tsx | 138 +++++++++--------- .../components/EmptyCorrelationsCTA.tsx | 2 +- .../variables/VariableSetEditableElement.tsx | 29 ++-- .../TransformationPicker.tsx | 3 +- .../components/DataSourceTypeCard.tsx | 2 +- .../components/DataSourcesList.tsx | 2 +- .../components/DataSourcesListCard.tsx | 4 +- .../components/picker/DataSourceCard.tsx | 7 +- .../features/explore/CorrelationHelper.tsx | 16 +- .../PublicDashboardListTable.tsx | 5 +- public/app/features/playlist/PlaylistCard.tsx | 6 +- .../features/playlist/PlaylistPageList.tsx | 1 + .../admin/components/ConnectionsTab.tsx | 2 +- .../provisioning/File/FileHistoryPage.tsx | 2 +- .../LogsQueryEditor/AzureCheatSheet.tsx | 91 ++++++------ .../querybuilder/components/QueryPattern.tsx | 2 +- .../components/QueryPatternsModal.tsx | 12 +- .../panel/annolist/AnnotationListItem.tsx | 4 +- 33 files changed, 191 insertions(+), 309 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 8f0d21a4b5c..0fc60f1d5e0 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -420,11 +420,6 @@ "count": 1 } }, - "packages/grafana-prometheus/src/querybuilder/QueryPattern.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "packages/grafana-prometheus/src/querybuilder/components/LabelFilterItem.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 4 @@ -1229,16 +1224,6 @@ "count": 1 } }, - "public/app/core/components/AppChrome/History/HistoryWrapper.tsx": { - "no-restricted-syntax": { - "count": 2 - } - }, - "public/app/core/components/AppNotifications/StoredNotificationItem.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/core/components/DynamicImports/SafeDynamicImport.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -1285,11 +1270,6 @@ "count": 1 } }, - "public/app/core/components/NavLandingPage/NavLandingPageCard.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/core/components/OptionsUI/NumberInput.tsx": { "no-restricted-syntax": { "count": 1 @@ -1540,11 +1520,6 @@ "count": 1 } }, - "public/app/features/admin/ServerStatsCard.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/admin/UserCreatePage.tsx": { "no-restricted-syntax": { "count": 4 @@ -1624,16 +1599,6 @@ "count": 2 } }, - "public/app/features/alerting/unified/AlertsFolderView.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, - "public/app/features/alerting/unified/RedirectToRuleViewer.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/alerting/unified/components/AlertLabelDropdown.tsx": { "no-restricted-syntax": { "count": 1 @@ -1745,11 +1710,6 @@ "count": 1 } }, - "public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/alerting/unified/components/receivers/form/GrafanaCommonChannelSettings.tsx": { "no-restricted-syntax": { "count": 1 @@ -1836,9 +1796,6 @@ "@typescript-eslint/consistent-type-assertions": { "count": 1 }, - "no-restricted-syntax": { - "count": 1 - }, "react-prefer-function-component/react-prefer-function-component": { "count": 1 } @@ -1883,11 +1840,6 @@ "count": 1 } }, - "public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/alerting/unified/components/rules/Filter/RulesFilter.v1.tsx": { "no-restricted-syntax": { "count": 4 @@ -1913,11 +1865,6 @@ "count": 1 } }, - "public/app/features/alerting/unified/components/settings/AlertmanagerCard.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/alerting/unified/components/silences/MatchersField.tsx": { "no-restricted-syntax": { "count": 5 @@ -1984,11 +1931,6 @@ "count": 6 } }, - "public/app/features/alerting/unified/rule-list/FilterViewStatus.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/alerting/unified/types/alerting.ts": { "@typescript-eslint/no-explicit-any": { "count": 5 @@ -2136,11 +2078,6 @@ "count": 1 } }, - "public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/connections/tabs/ConnectData/CardGrid/index.tsx": { "no-barrel-files/no-barrel-files": { "count": 1 @@ -2187,11 +2124,6 @@ "count": 2 } }, - "public/app/features/correlations/Forms/ConfigureCorrelationSourceForm.tsx": { - "no-restricted-syntax": { - "count": 3 - } - }, "public/app/features/correlations/Forms/ConfigureCorrelationTargetForm.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 @@ -2210,11 +2142,6 @@ "count": 4 } }, - "public/app/features/correlations/components/EmptyCorrelationsCTA.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/correlations/types.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 @@ -2491,11 +2418,6 @@ "count": 4 } }, - "public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/dashboard-scene/settings/variables/components/AdHocVariableForm.tsx": { "no-restricted-syntax": { "count": 1 @@ -2880,11 +2802,6 @@ "count": 1 } }, - "public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 @@ -3078,19 +2995,6 @@ "public/app/features/datasources/components/DataSourceTypeCard.tsx": { "@grafana/no-aria-label-selectors": { "count": 1 - }, - "no-restricted-syntax": { - "count": 1 - } - }, - "public/app/features/datasources/components/DataSourcesListCard.tsx": { - "no-restricted-syntax": { - "count": 2 - } - }, - "public/app/features/datasources/components/picker/DataSourceCard.tsx": { - "no-restricted-syntax": { - "count": 1 } }, "public/app/features/datasources/state/actions.test.ts": { @@ -3185,7 +3089,7 @@ }, "public/app/features/explore/CorrelationHelper.tsx": { "no-restricted-syntax": { - "count": 3 + "count": 2 } }, "public/app/features/explore/CorrelationTransformationAddModal.tsx": { @@ -3558,11 +3462,6 @@ "count": 1 } }, - "public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/manage-dashboards/state/actions.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -3649,11 +3548,6 @@ "count": 1 } }, - "public/app/features/playlist/PlaylistCard.tsx": { - "no-restricted-syntax": { - "count": 2 - } - }, "public/app/features/playlist/PlaylistForm.tsx": { "@grafana/no-aria-label-selectors": { "count": 2 @@ -3793,11 +3687,6 @@ "count": 2 } }, - "public/app/features/provisioning/File/FileHistoryPage.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/provisioning/File/FileStatusPage.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 @@ -4392,7 +4281,7 @@ }, "public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx": { "no-restricted-syntax": { - "count": 2 + "count": 1 } }, "public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/index.tsx": { @@ -4970,11 +4859,6 @@ "count": 1 } }, - "public/app/plugins/datasource/loki/querybuilder/components/QueryPattern.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/plugins/datasource/loki/querybuilder/state.ts": { "no-restricted-syntax": { "count": 2 @@ -5148,11 +5032,6 @@ "count": 1 } }, - "public/app/plugins/panel/annolist/AnnotationListItem.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/plugins/panel/barchart/bars.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 diff --git a/packages/grafana-prometheus/src/querybuilder/QueryPattern.tsx b/packages/grafana-prometheus/src/querybuilder/QueryPattern.tsx index b61f64ec58d..5d8a74f787a 100644 --- a/packages/grafana-prometheus/src/querybuilder/QueryPattern.tsx +++ b/packages/grafana-prometheus/src/querybuilder/QueryPattern.tsx @@ -28,7 +28,7 @@ export const QueryPattern = (props: Props) => { const lang = { grammar: promqlGrammar, name: 'promql' }; return ( - <Card className={styles.card}> + <Card noMargin className={styles.card}> <Card.Heading>{pattern.name}</Card.Heading> <div className={styles.rawQueryContainer}> <RawQuery diff --git a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx index 565eef17121..a4f17087b21 100644 --- a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx +++ b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx @@ -6,7 +6,7 @@ import { useMemo, useState } from 'react'; import { CoreApp, DataQuery, getNextRefId, GrafanaTheme2 } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; import { reportInteraction } from '@grafana/runtime'; -import { Button, Collapse, Modal, useStyles2 } from '@grafana/ui'; +import { Button, Collapse, Modal, Stack, useStyles2 } from '@grafana/ui'; import { PromQuery } from '../types'; @@ -125,7 +125,7 @@ export const QueryPatternsModal = (props: Props) => { ); }} > - <div className={styles.cardsContainer}> + <Stack wrap justifyContent="space-between"> {promQueryModeller .getQueryPatterns() .filter((pattern) => pattern.type === patternType) @@ -140,7 +140,7 @@ export const QueryPatternsModal = (props: Props) => { setSelectedPatternName={setSelectedPatternName} /> ))} - </div> + </Stack> </Collapse> ); })} @@ -160,12 +160,6 @@ export const QueryPatternsModal = (props: Props) => { const getStyles = (theme: GrafanaTheme2) => { return { - cardsContainer: css({ - display: 'flex', - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'space-between', - }), spacing: css({ marginBottom: theme.spacing(1), }), diff --git a/public/app/core/components/AppChrome/History/HistoryWrapper.tsx b/public/app/core/components/AppChrome/History/HistoryWrapper.tsx index f6120e81c8c..d25c86d6f3d 100644 --- a/public/app/core/components/AppChrome/History/HistoryWrapper.tsx +++ b/public/app/core/components/AppChrome/History/HistoryWrapper.tsx @@ -4,7 +4,7 @@ import { useState } from 'react'; import { FieldType, GrafanaTheme2, store } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { Button, Card, IconButton, Space, Stack, Text, useStyles2, Box, Sparkline, useTheme2, Icon } from '@grafana/ui'; +import { Box, Button, Card, Icon, IconButton, Space, Sparkline, Stack, Text, useStyles2, useTheme2 } from '@grafana/ui'; import { formatDate } from 'app/core/internationalization/dates'; import { HISTORY_LOCAL_STORAGE_KEY } from '../AppChromeService'; @@ -123,6 +123,7 @@ function HistoryEntryAppView({ entry, isSelected, onClick }: ItemProps) { className={isExpanded ? styles.iconButtonDot : styles.iconButtonCircle} /> <Card + noMargin onClick={() => { store.setObject('CLICKING_HISTORY', true); onClick(); @@ -182,6 +183,7 @@ function HistoryEntryAppView({ entry, isSelected, onClick }: ItemProps) { return ( <Card key={index} + noMargin href={view.url} onClick={() => { store.setObject('CLICKING_HISTORY', true); diff --git a/public/app/core/components/AppNotifications/StoredNotificationItem.tsx b/public/app/core/components/AppNotifications/StoredNotificationItem.tsx index 2592d978175..c3df7d40596 100644 --- a/public/app/core/components/AppNotifications/StoredNotificationItem.tsx +++ b/public/app/core/components/AppNotifications/StoredNotificationItem.tsx @@ -32,7 +32,7 @@ export const StoredNotificationItem = ({ const styles = getStyles(theme); return ( - <Card className={className} onClick={onClick}> + <Card noMargin className={className} onClick={onClick}> <Card.Heading>{title}</Card.Heading> <Card.Description>{children}</Card.Description> <Card.Figure> diff --git a/public/app/core/components/NavLandingPage/NavLandingPageCard.tsx b/public/app/core/components/NavLandingPage/NavLandingPageCard.tsx index 0b24a12b524..4eb223b4f59 100644 --- a/public/app/core/components/NavLandingPage/NavLandingPageCard.tsx +++ b/public/app/core/components/NavLandingPage/NavLandingPageCard.tsx @@ -12,7 +12,7 @@ interface Props { export function NavLandingPageCard({ description, text, url }: Props) { const styles = useStyles2(getStyles); return ( - <Card className={styles.card} href={url}> + <Card noMargin className={styles.card} href={url}> <Card.Heading>{text}</Card.Heading> <Card.Description className={styles.description}>{description}</Card.Description> </Card> @@ -21,7 +21,6 @@ export function NavLandingPageCard({ description, text, url }: Props) { const getStyles = (theme: GrafanaTheme2) => ({ card: css({ - marginBottom: 0, gridTemplateRows: '1fr 0 2fr', }), // Limit descriptions to 3 lines max before ellipsing diff --git a/public/app/features/admin/ServerStatsCard.tsx b/public/app/features/admin/ServerStatsCard.tsx index c9ae7e3b2f4..070005e7e3a 100644 --- a/public/app/features/admin/ServerStatsCard.tsx +++ b/public/app/features/admin/ServerStatsCard.tsx @@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css'; import Skeleton from 'react-loading-skeleton'; import { GrafanaTheme2 } from '@grafana/data'; -import { Card, Stack, useStyles2, Tooltip, Icon } from '@grafana/ui'; +import { Card, Icon, Stack, Tooltip, useStyles2 } from '@grafana/ui'; interface StatItem { name: string; @@ -21,7 +21,7 @@ export interface Props { export const ServerStatsCard = ({ content, footer, isLoading }: Props) => { const styles = useStyles2(getStyles); return ( - <Card className={styles.container}> + <Card noMargin className={styles.container}> {content.map((item, index) => ( <Stack key={index} justifyContent="space-between" alignItems="center"> <Stack alignItems={'center'}> @@ -50,7 +50,6 @@ const getStyles = (theme: GrafanaTheme2) => { display: 'flex', flexDirection: 'column', gap: theme.spacing(2), - margin: 0, padding: theme.spacing(2), }), indent: css({ diff --git a/public/app/features/alerting/unified/AlertsFolderView.tsx b/public/app/features/alerting/unified/AlertsFolderView.tsx index 01841e51364..9174af5a022 100644 --- a/public/app/features/alerting/unified/AlertsFolderView.tsx +++ b/public/app/features/alerting/unified/AlertsFolderView.tsx @@ -83,10 +83,11 @@ export const AlertsFolderView = ({ folder, rules }: Props) => { /> </Stack> - <Stack direction="column" gap={1}> + <Stack direction="column"> {pageItems.map(({ grafana_alert, labels = {} }) => ( <Card key={grafana_alert.uid} + noMargin href={createGrafanaRuleViewLink(grafana_alert)} className={styles.card} data-testid="alert-card-row" @@ -200,7 +201,6 @@ export const getStyles = (theme: GrafanaTheme2) => ({ }), card: css({ gridTemplateColumns: 'auto 1fr 2fr', - margin: 0, }), pagination: css({ alignSelf: 'center', diff --git a/public/app/features/alerting/unified/RedirectToRuleViewer.tsx b/public/app/features/alerting/unified/RedirectToRuleViewer.tsx index 33c8f7918a4..96410c28320 100644 --- a/public/app/features/alerting/unified/RedirectToRuleViewer.tsx +++ b/public/app/features/alerting/unified/RedirectToRuleViewer.tsx @@ -130,7 +130,7 @@ export function RedirectToRuleViewer(): JSX.Element | null { <div className={styles.rules}> {rules.map((rule, index) => { return ( - <Card key={`${rule.name}-${index}`} href={createViewLink(rulesSource, rule, '/alerting/list')}> + <Card noMargin key={`${rule.name}-${index}`} href={createViewLink(rulesSource, rule, '/alerting/list')}> <Card.Heading>{rule.name}</Card.Heading> <Card.Meta separator={''}> <Icon name="folder" /> 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 127b59256f0..9d629cee17c 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx @@ -102,7 +102,7 @@ export const GenerateAlertDataModal = ({ isOpen, onDismiss, onAccept }: Props) = setStatus('firing'); }} > - <Card> + <Card noMargin> <Stack direction="column" gap={1}> <div className={styles.section}> <AnnotationsStep /> diff --git a/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx b/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx index bf35a7ae018..0bdd3089eb8 100644 --- a/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx @@ -283,7 +283,7 @@ const DatasourceNotFound = ({ index, onUpdateDatasource, onRemoveQuery, model }: return ( <EmptyQueryWrapper> <QueryOperationRow title={refId} draggable index={index} id={refId} isOpen collapsable={false}> - <Card> + <Card noMargin> <Card.Heading> <Trans i18nKey="alerting.datasource-not-found.this-datasource-has-been-removed"> This datasource has been removed diff --git a/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx b/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx index 3568fa5985c..05d3657d549 100644 --- a/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/rule-types/RuleType.tsx @@ -30,7 +30,7 @@ const RuleType = (props: Props) => { }); return ( - <Card className={cardStyles} isSelected={selected} onClick={() => onClick(value)} disabled={disabled}> + <Card noMargin className={cardStyles} isSelected={selected} onClick={() => onClick(value)} disabled={disabled}> <Card.Figure> <img src={image} alt="" /> </Card.Figure> diff --git a/public/app/features/alerting/unified/components/settings/AlertmanagerCard.tsx b/public/app/features/alerting/unified/components/settings/AlertmanagerCard.tsx index b38cd2188b8..36013c290c5 100644 --- a/public/app/features/alerting/unified/components/settings/AlertmanagerCard.tsx +++ b/public/app/features/alerting/unified/components/settings/AlertmanagerCard.tsx @@ -43,7 +43,7 @@ export function AlertmanagerCard({ const showActions = !provisioned && Boolean(onEnable) && Boolean(onDisable); return ( - <Card data-testid={`alertmanager-card-${name}`}> + <Card noMargin data-testid={`alertmanager-card-${name}`}> <Card.Heading> <Stack alignItems="center" gap={1}> {href ? ( diff --git a/public/app/features/alerting/unified/rule-list/FilterViewStatus.tsx b/public/app/features/alerting/unified/rule-list/FilterViewStatus.tsx index d01abb822b8..aef6553e0f2 100644 --- a/public/app/features/alerting/unified/rule-list/FilterViewStatus.tsx +++ b/public/app/features/alerting/unified/rule-list/FilterViewStatus.tsx @@ -10,7 +10,7 @@ interface FilterStatusProps { export function FilterStatus({ state, numberOfRules, onCancel }: FilterStatusProps) { return ( - <Card> + <Card noMargin> <Text color="secondary"> {/* done searching everything and found some results */} {state === 'done' && ( diff --git a/public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx b/public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx index 80b08bb663e..ce1e9b51681 100644 --- a/public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx +++ b/public/app/features/connections/tabs/ConnectData/CardGrid/CardGrid.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { t } from '@grafana/i18n'; import { featureEnabled } from '@grafana/runtime'; -import { Card, Grid, useStyles2, Stack, Badge } from '@grafana/ui'; +import { Badge, Card, Grid, Stack, useStyles2 } from '@grafana/ui'; import { PluginDeprecatedBadge } from 'app/features/plugins/admin/components/Badges/PluginDeprecatedBadge'; import { PluginDisabledBadge } from 'app/features/plugins/admin/components/Badges/PluginDisabledBadge'; import { PluginInstalledBadge } from 'app/features/plugins/admin/components/Badges/PluginInstallBadge'; @@ -85,6 +85,7 @@ export const CardGrid = ({ items, onClickItem }: CardGridProps) => { {items.map((item) => ( <Card key={item.id} + noMargin className={styles.card} href={item.url} onClick={(e) => { diff --git a/public/app/features/correlations/Forms/ConfigureCorrelationSourceForm.tsx b/public/app/features/correlations/Forms/ConfigureCorrelationSourceForm.tsx index 830684e8704..75c0ee9e353 100644 --- a/public/app/features/correlations/Forms/ConfigureCorrelationSourceForm.tsx +++ b/public/app/features/correlations/Forms/ConfigureCorrelationSourceForm.tsx @@ -3,7 +3,7 @@ import { Controller, useFormContext } from 'react-hook-form'; import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { Card, Field, FieldSet, Input, useStyles2 } from '@grafana/ui'; +import { Card, Field, FieldSet, Input, Stack, useStyles2 } from '@grafana/ui'; import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; @@ -94,74 +94,78 @@ export const ConfigureCorrelationSourceForm = () => { variables. </p> </Trans> - <Controller - control={control} - name="sourceUID" - rules={{ - required: { - value: true, - message: t('correlations.source-form.control-required', 'This field is required.'), - }, - }} - render={({ field: { onChange, value } }) => ( - <Field - label={t('correlations.source-form.source-label', 'Source')} - description={t( - 'correlations.source-form.source-description', - 'Results from selected source data source have links displayed in the panel' - )} - htmlFor="source" - invalid={!!formState.errors.sourceUID} - error={formState.errors.sourceUID?.message} - > - <DataSourcePicker - onChange={withDsUID(onChange)} - noDefault - current={value} - inputId="source" - width={32} - disabled={correlation !== undefined} - /> - </Field> - )} - /> - - <Field - label={t('correlations.source-form.results-label', 'Results field')} - description={t( - 'correlations.source-form.results-description', - 'The link will be shown next to the value of this field' - )} - className={styles.label} - invalid={!!formState.errors?.config?.field} - error={formState.errors?.config?.field?.message} - > - <Input - id={getInputId('field', correlation)} - {...register('config.field', { - required: t('correlations.source-form.results-required', 'This field is required.'), - })} - readOnly={readOnly} + <Stack direction="column" gap={2}> + <Controller + control={control} + name="sourceUID" + rules={{ + required: { + value: true, + message: t('correlations.source-form.control-required', 'This field is required.'), + }, + }} + render={({ field: { onChange, value } }) => ( + <Field + noMargin + label={t('correlations.source-form.source-label', 'Source')} + description={t( + 'correlations.source-form.source-description', + 'Results from selected source data source have links displayed in the panel' + )} + htmlFor="source" + invalid={!!formState.errors.sourceUID} + error={formState.errors.sourceUID?.message} + > + <DataSourcePicker + onChange={withDsUID(onChange)} + noDefault + current={value} + inputId="source" + width={32} + disabled={correlation !== undefined} + /> + </Field> + )} /> - </Field> - {variables.length > 0 && ( - <Card> - <Card.Heading>{formText.heading}</Card.Heading> - <Card.Description> - {formText.descriptionPre} - <VariableList /> - <br /> - <Trans i18nKey="correlations.source-form.description"> - A data point needs to provide values to all variables as fields or as transformations output to make the - correlation button appear in the visualization. + + <Field + noMargin + label={t('correlations.source-form.results-label', 'Results field')} + description={t( + 'correlations.source-form.results-description', + 'The link will be shown next to the value of this field' + )} + className={styles.label} + invalid={!!formState.errors?.config?.field} + error={formState.errors?.config?.field?.message} + > + <Input + id={getInputId('field', correlation)} + {...register('config.field', { + required: t('correlations.source-form.results-required', 'This field is required.'), + })} + readOnly={readOnly} + /> + </Field> + {variables.length > 0 && ( + <Card noMargin> + <Card.Heading>{formText.heading}</Card.Heading> + <Card.Description> + {formText.descriptionPre} + <VariableList /> <br /> - Note: Not every variable needs to be explicitly defined below. A transformation such as{' '} - <span className={styles.variable}>logfmt</span> will create variables for every key/value pair. - </Trans> - </Card.Description> - </Card> - )} - <TransformationsEditor readOnly={readOnly} /> + <Trans i18nKey="correlations.source-form.description"> + A data point needs to provide values to all variables as fields or as transformations output to make + the correlation button appear in the visualization. + <br /> + Note: Not every variable needs to be explicitly defined below. A transformation such as{' '} + <span className={styles.variable}>logfmt</span> will create variables for every key/value pair. + </Trans> + </Card.Description> + </Card> + )} + <TransformationsEditor readOnly={readOnly} /> + </Stack> </FieldSet> </> ); diff --git a/public/app/features/correlations/components/EmptyCorrelationsCTA.tsx b/public/app/features/correlations/components/EmptyCorrelationsCTA.tsx index cf677f2b992..8e9c7a65b63 100644 --- a/public/app/features/correlations/components/EmptyCorrelationsCTA.tsx +++ b/public/app/features/correlations/components/EmptyCorrelationsCTA.tsx @@ -23,7 +23,7 @@ export const EmptyCorrelationsCTA = ({ onClick, canWriteCorrelations }: Props) = </Trans> </EmptyState> ) : ( - <Card> + <Card noMargin> <Card.Heading> <Trans i18nKey="correlations.empty-correlations-cta.there-are-no-correlations-configured-yet"> There are no correlations configured yet. diff --git a/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx b/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx index 9003f9fff45..0cefe5cec8a 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableSetEditableElement.tsx @@ -6,7 +6,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { SceneVariable, SceneVariableSet } from '@grafana/scenes'; -import { Stack, Button, useStyles2, Text, Box, Card } from '@grafana/ui'; +import { Box, Button, Card, Stack, Text, useStyles2 } from '@grafana/ui'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; @@ -130,18 +130,21 @@ function VariableTypeSelection({ onAddVariable }: VariableTypeSelectionProps) { <Box paddingBottom={1} display={'flex'}> <Trans i18nKey="dashboard.edit-pane.variables.select-type">Choose variable type</Trans> </Box> - {options.map((option) => ( - <Card - isCompact - onClick={() => onAddVariable(option.value!)} - key={option.value} - title={t('dashboard.edit-pane.variables.select-type-card-tooltip', 'Click to select type')} - data-testid={selectors.components.PanelEditor.ElementEditPane.variableType(option.value!)} - > - <Card.Heading>{option.label}</Card.Heading> - <Card.Description className={styles.cardDescription}>{option.description}</Card.Description> - </Card> - ))} + <Stack direction="column"> + {options.map((option) => ( + <Card + isCompact + noMargin + onClick={() => onAddVariable(option.value!)} + key={option.value} + title={t('dashboard.edit-pane.variables.select-type-card-tooltip', 'Click to select type')} + data-testid={selectors.components.PanelEditor.ElementEditPane.variableType(option.value!)} + > + <Card.Heading>{option.label}</Card.Heading> + <Card.Description className={styles.cardDescription}>{option.description}</Card.Description> + </Card> + ))} + </Stack> </Stack> ); } diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx index 2939ff59ad7..d1c4ca79d95 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationPicker.tsx @@ -4,7 +4,7 @@ import { FormEventHandler, KeyboardEventHandler, ReactNode } from 'react'; import { DocsId, GrafanaTheme2, LocalStorageValueProvider, TransformerRegistryItem } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; -import { Card, Container, Alert, Input, useStyles2, Stack } from '@grafana/ui'; +import { Alert, Card, Container, Input, Stack, useStyles2 } from '@grafana/ui'; import { getDocsLink } from 'app/core/utils/docsLinks'; import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo'; @@ -107,6 +107,7 @@ function TransformationCard({ transform, onClick }: TransformationCardProps) { const styles = useStyles2(getStyles); return ( <Card + noMargin className={styles.card} data-testid={selectors.components.TransformTab.newTransform(transform.name)} onClick={onClick} diff --git a/public/app/features/datasources/components/DataSourceTypeCard.tsx b/public/app/features/datasources/components/DataSourceTypeCard.tsx index 2bda4985cea..c7ce0260d88 100644 --- a/public/app/features/datasources/components/DataSourceTypeCard.tsx +++ b/public/app/features/datasources/components/DataSourceTypeCard.tsx @@ -19,7 +19,7 @@ export function DataSourceTypeCard({ onClick, dataSourcePlugin }: Props) { const styles = useStyles2(getStyles); return ( - <Card className={cx(styles.card, 'card-parent')} onClick={isClickable ? onClick : () => {}}> + <Card noMargin className={cx(styles.card, 'card-parent')} onClick={isClickable ? onClick : () => {}}> {/* Name */} <Card.Heading className={styles.heading} diff --git a/public/app/features/datasources/components/DataSourcesList.tsx b/public/app/features/datasources/components/DataSourcesList.tsx index 94e8135a872..8113bba85e0 100644 --- a/public/app/features/datasources/components/DataSourcesList.tsx +++ b/public/app/features/datasources/components/DataSourcesList.tsx @@ -160,7 +160,7 @@ const getStyles = (theme: GrafanaTheme2) => { list: css({ listStyle: 'none', display: 'grid', - // gap: '8px', Add back when legacy support for old Card interface is dropped + gap: theme.spacing(1), }), }; }; diff --git a/public/app/features/datasources/components/DataSourcesListCard.tsx b/public/app/features/datasources/components/DataSourcesListCard.tsx index 3c9d0424d9a..39a50961412 100644 --- a/public/app/features/datasources/components/DataSourcesListCard.tsx +++ b/public/app/features/datasources/components/DataSourcesListCard.tsx @@ -21,7 +21,7 @@ export function DataSourcesListCard({ dataSource, hasWriteRights, hasExploreRigh const styles = useStyles2(getStyles); return ( - <Card href={hasWriteRights ? dsLink : undefined}> + <Card noMargin href={hasWriteRights ? dsLink : undefined}> <Card.Heading>{dataSource.name}</Card.Heading> <Card.Figure> <img src={dataSource.typeLogoUrl} alt="" height="40px" width="40px" className={styles.logo} /> @@ -80,7 +80,7 @@ export function DataSourcesListCard({ dataSource, hasWriteRights, hasExploreRigh function DataSourcesListCardSkeleton({ hasExploreRights }: Pick<Props, 'hasExploreRights'>) { const skeletonStyles = useStyles2(getSkeletonStyles); return ( - <Card> + <Card noMargin> <Card.Heading> <Skeleton width={140} /> </Card.Heading> diff --git a/public/app/features/datasources/components/picker/DataSourceCard.tsx b/public/app/features/datasources/components/picker/DataSourceCard.tsx index c1a5f4d3345..3f2a429a786 100644 --- a/public/app/features/datasources/components/picker/DataSourceCard.tsx +++ b/public/app/features/datasources/components/picker/DataSourceCard.tsx @@ -1,7 +1,7 @@ import { css, cx } from '@emotion/css'; import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data'; -import { Card, TagList, useTheme2, Icon } from '@grafana/ui'; +import { Card, Icon, TagList, useTheme2 } from '@grafana/ui'; interface DataSourceCardProps { ds: DataSourceInstanceSettings; @@ -27,6 +27,7 @@ export function DataSourceCard({ return ( <Card key={ds.uid} + noMargin onClick={onClick} className={cx(styles.card, selected ? styles.selected : undefined)} {...htmlProps} @@ -64,8 +65,6 @@ function getStyles(theme: GrafanaTheme2, builtIn = false) { card: css({ cursor: 'pointer', backgroundColor: 'transparent', - // Move to list component - marginBottom: 0, padding: theme.spacing(1), '&:hover': { @@ -75,7 +74,7 @@ function getStyles(theme: GrafanaTheme2, builtIn = false) { heading: css({ width: '100%', overflow: 'hidden', - // This is needed to enable ellipsis when text overlfows + // This is needed to enable ellipsis when text overflows '> button': { width: '100%', }, diff --git a/public/app/features/explore/CorrelationHelper.tsx b/public/app/features/explore/CorrelationHelper.tsx index bf57509bf63..9a323e6a47c 100644 --- a/public/app/features/explore/CorrelationHelper.tsx +++ b/public/app/features/explore/CorrelationHelper.tsx @@ -1,23 +1,23 @@ import { css } from '@emotion/css'; -import { useState, useEffect, useId } from 'react'; +import { useEffect, useId, useState } from 'react'; import { useForm } from 'react-hook-form'; import { useAsync } from 'react-use'; import { DataLinkTransformationConfig, ExploreCorrelationHelperData, GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { - Collapse, Alert, - Field, - Input, Button, Card, - IconButton, - useStyles2, + Collapse, DeleteButton, - Tooltip, + Field, Icon, + IconButton, + Input, Stack, + Tooltip, + useStyles2, } from '@grafana/ui'; import { useDispatch, useSelector } from 'app/types/store'; @@ -241,7 +241,7 @@ export const CorrelationHelper = ({ exploreId, correlations }: Props) => { ) : undefined, ].filter((val) => val); return ( - <Card key={`trans-${i}`}> + <Card noMargin key={`trans-${i}`}> <Card.Heading> {field}: {type} </Card.Heading> diff --git a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx index 642d2c64ac7..d14fd970d1d 100644 --- a/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx +++ b/public/app/features/manage-dashboards/components/PublicDashboardListTable/PublicDashboardListTable.tsx @@ -59,7 +59,7 @@ const PublicDashboardCard = ({ pd }: { pd: PublicDashboardListResponse }) => { const translatedPauseSharingText = t('shared-dashboard-list.toggle.pause-sharing-toggle-text', 'Pause access'); return ( - <Card className={styles.card} href={`/d/${pd.dashboardUid}`}> + <Card noMargin className={styles.card} href={`/d/${pd.dashboardUid}`}> <Card.Heading className={styles.heading}> <span>{pd.title}</span> </Card.Heading> @@ -173,6 +173,9 @@ const getStyles = (theme: GrafanaTheme2) => ({ list: css({ listStyleType: 'none', marginBottom: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), }), card: css({ [theme.breakpoints.up('sm')]: { diff --git a/public/app/features/playlist/PlaylistCard.tsx b/public/app/features/playlist/PlaylistCard.tsx index b0bfb548c81..6eb5202563b 100644 --- a/public/app/features/playlist/PlaylistCard.tsx +++ b/public/app/features/playlist/PlaylistCard.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import Skeleton from 'react-loading-skeleton'; import { GrafanaTheme2 } from '@grafana/data'; -import { Trans, t } from '@grafana/i18n'; +import { t, Trans } from '@grafana/i18n'; import { Button, Card, LinkButton, ModalsController, Stack, useStyles2 } from '@grafana/ui'; import { attachSkeleton, SkeletonComponent } from '@grafana/ui/unstable'; import { contextSrv } from 'app/core/services/context_srv'; @@ -20,7 +20,7 @@ interface Props { const PlaylistCardComponent = ({ playlist, setStartPlaylist, setPlaylistToDelete }: Props) => { return ( - <Card> + <Card noMargin> <Card.Heading> {playlist.spec?.title} <ModalsController key="button-share"> @@ -66,7 +66,7 @@ const PlaylistCardComponent = ({ playlist, setStartPlaylist, setPlaylistToDelete const PlaylistCardSkeleton: SkeletonComponent = ({ rootProps }) => { const skeletonStyles = useStyles2(getSkeletonStyles); return ( - <Card {...rootProps}> + <Card noMargin {...rootProps}> <Card.Heading> <Skeleton width={140} /> </Card.Heading> diff --git a/public/app/features/playlist/PlaylistPageList.tsx b/public/app/features/playlist/PlaylistPageList.tsx index 49b4cc6c51a..868b21e1453 100644 --- a/public/app/features/playlist/PlaylistPageList.tsx +++ b/public/app/features/playlist/PlaylistPageList.tsx @@ -48,6 +48,7 @@ function getStyles(theme: GrafanaTheme2) { return { list: css({ display: 'grid', + gap: theme.spacing(1), }), listItem: css({ listStyle: 'none', diff --git a/public/app/features/plugins/admin/components/ConnectionsTab.tsx b/public/app/features/plugins/admin/components/ConnectionsTab.tsx index 70a2a26f900..309cfdef7da 100644 --- a/public/app/features/plugins/admin/components/ConnectionsTab.tsx +++ b/public/app/features/plugins/admin/components/ConnectionsTab.tsx @@ -103,7 +103,7 @@ const getStyles = (theme: GrafanaTheme2) => { list: css({ listStyle: 'none', display: 'grid', - // gap: '8px', Add back when legacy support for old Card interface is dropped + gap: theme.spacing(1), }), }; }; diff --git a/public/app/features/provisioning/File/FileHistoryPage.tsx b/public/app/features/provisioning/File/FileHistoryPage.tsx index f1a4274ada7..6c187d99126 100644 --- a/public/app/features/provisioning/File/FileHistoryPage.tsx +++ b/public/app/features/provisioning/File/FileHistoryPage.tsx @@ -78,7 +78,7 @@ function HistoryView({ history, path, repo }: Props) { return ( <Stack direction={'column'}> {history.items.map((item) => ( - <Card href={`${PROVISIONING_URL}/${repo}/file/${path}?ref=${item.ref}`} key={item.ref}> + <Card noMargin href={`${PROVISIONING_URL}/${repo}/file/${path}?ref=${item.ref}`} key={item.ref}> <Card.Heading>{item.message}</Card.Heading> <Card.Meta> <span>{formatTimestamp(item.createdAt)}</span> diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx index 204bc43d984..5116a7b86fb 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx @@ -13,6 +13,7 @@ import { LoadingPlaceholder, ScrollContainer, Select, + Stack, useStyles2, } from '@grafana/ui'; @@ -190,50 +191,52 @@ const AzureCheatSheet = (props: AzureCheatSheetProps) => { onToggle={(isOpen) => setAreDropdownsOpen({ ...areDropdownsOpen, [category]: isOpen })} key={category} > - {visibleQueries[category]!.map((query) => { - return ( - <Card className={styles.card} key={query.id}> - <Card.Heading>{query.displayName}</Card.Heading> - <ScrollContainer showScrollIndicators maxHeight="100px"> - <RawQuery - aria-label={t( - 'components.azure-cheat-sheet.aria-label-raw-query', - '{{queryDisplayName}} raw query', - { queryDisplayName: query.displayName } - )} - query={query.body} - lang={lang} - className={styles.rawQuery} - /> - </ScrollContainer> - <Card.Actions> - <Button - size="sm" - aria-label={t( - 'components.azure-cheat-sheet.aria-label-use-query', - 'Use this query button' - )} - onClick={() => { - props.onChange({ - refId: 'A', - queryType: AzureQueryType.LogAnalytics, - azureLogAnalytics: { query: query.body }, - datasource: props.datasource, - }); - reportInteraction('grafana_azure_cheatsheet_logs_query_selected', { - id: query.id, - queryName: query.displayName, - query: query.body, - queryCategories: query.related.categories, - }); - }} - > - <Trans i18nKey="components.azure-cheat-sheet.button-use-query">Use this query</Trans> - </Button> - </Card.Actions> - </Card> - ); - })} + <Stack direction="column"> + {visibleQueries[category]!.map((query) => { + return ( + <Card noMargin className={styles.card} key={query.id}> + <Card.Heading>{query.displayName}</Card.Heading> + <ScrollContainer showScrollIndicators maxHeight="100px"> + <RawQuery + aria-label={t( + 'components.azure-cheat-sheet.aria-label-raw-query', + '{{queryDisplayName}} raw query', + { queryDisplayName: query.displayName } + )} + query={query.body} + lang={lang} + className={styles.rawQuery} + /> + </ScrollContainer> + <Card.Actions> + <Button + size="sm" + aria-label={t( + 'components.azure-cheat-sheet.aria-label-use-query', + 'Use this query button' + )} + onClick={() => { + props.onChange({ + refId: 'A', + queryType: AzureQueryType.LogAnalytics, + azureLogAnalytics: { query: query.body }, + datasource: props.datasource, + }); + reportInteraction('grafana_azure_cheatsheet_logs_query_selected', { + id: query.id, + queryName: query.displayName, + query: query.body, + queryCategories: query.related.categories, + }); + }} + > + <Trans i18nKey="components.azure-cheat-sheet.button-use-query">Use this query</Trans> + </Button> + </Card.Actions> + </Card> + ); + })} + </Stack> </Collapse> ); } diff --git a/public/app/plugins/datasource/loki/querybuilder/components/QueryPattern.tsx b/public/app/plugins/datasource/loki/querybuilder/components/QueryPattern.tsx index 7edaf3ead6d..28e3b888c99 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/QueryPattern.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/QueryPattern.tsx @@ -25,7 +25,7 @@ export const QueryPattern = (props: Props) => { const lang = { grammar: logqlGrammar, name: 'logql' }; return ( - <Card className={styles.card}> + <Card noMargin className={styles.card}> <Card.Heading>{pattern.name}</Card.Heading> <div className={styles.rawQueryContainer}> <RawQuery diff --git a/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx b/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx index 42d67a19c89..9be0cc98ec7 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx @@ -5,7 +5,7 @@ import { useMemo, useState } from 'react'; import { CoreApp, GrafanaTheme2, getNextRefId } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; -import { Button, Collapse, Modal, useStyles2 } from '@grafana/ui'; +import { Button, Collapse, Modal, Stack, useStyles2 } from '@grafana/ui'; import { LokiQuery } from '../../types'; import { lokiQueryModeller } from '../LokiQueryModeller'; @@ -107,7 +107,7 @@ export const QueryPatternsModal = (props: Props) => { ) } > - <div className={styles.cardsContainer}> + <Stack wrap justifyContent="space-between"> {lokiQueryModeller .getQueryPatterns() .filter((pattern) => pattern.type === patternType) @@ -122,7 +122,7 @@ export const QueryPatternsModal = (props: Props) => { setSelectedPatternName={setSelectedPatternName} /> ))} - </div> + </Stack> </Collapse> ); })} @@ -135,12 +135,6 @@ export const QueryPatternsModal = (props: Props) => { const getStyles = (theme: GrafanaTheme2) => { return { - cardsContainer: css({ - display: 'flex', - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'space-between', - }), spacing: css({ marginBottom: theme.spacing(1), }), diff --git a/public/app/plugins/panel/annolist/AnnotationListItem.tsx b/public/app/plugins/panel/annolist/AnnotationListItem.tsx index ecf22f9f597..0a321cbfbed 100644 --- a/public/app/plugins/panel/annolist/AnnotationListItem.tsx +++ b/public/app/plugins/panel/annolist/AnnotationListItem.tsx @@ -3,7 +3,7 @@ import { MouseEvent } from 'react'; import { AnnotationEvent, DateTimeInput, GrafanaTheme2, PanelProps } from '@grafana/data'; import { Trans } from '@grafana/i18n'; -import { Card, TagList, Tooltip, RenderUserContentAsHTML, useStyles2 } from '@grafana/ui'; +import { Card, RenderUserContentAsHTML, TagList, Tooltip, useStyles2 } from '@grafana/ui'; import { Options } from './panelcfg.gen'; @@ -30,7 +30,7 @@ export const AnnotationListItem = ({ options, annotation, formatDate, onClick, o const showTimeStampEnd = timeEnd && timeEnd !== time && showTime; return ( - <Card className={styles.card} onClick={onItemClick}> + <Card noMargin className={styles.card} onClick={onItemClick}> <Card.Heading> <RenderUserContentAsHTML className={styles.heading} From 0eb28d4f370520561d515ad895a92859550a1a7d Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE <gamab@users.noreply.github.com> Date: Thu, 16 Oct 2025 08:33:06 +0200 Subject: [PATCH 66/75] `grafana-iam`: Async write to zanzana (#112357) * `grafana-iam`: Async write to zanzana * More succint * Add a metric to keep track of waiting times for future calibration * metrics --- pkg/registry/apis/iam/hooks.go | 91 ++++++++++++++++------------- pkg/registry/apis/iam/hooks_test.go | 10 +++- pkg/registry/apis/iam/metrics.go | 32 ++++++++++ pkg/registry/apis/iam/models.go | 2 + pkg/registry/apis/iam/register.go | 11 ++-- 5 files changed, 99 insertions(+), 47 deletions(-) create mode 100644 pkg/registry/apis/iam/metrics.go diff --git a/pkg/registry/apis/iam/hooks.go b/pkg/registry/apis/iam/hooks.go index e8bdbc33515..f572711512b 100644 --- a/pkg/registry/apis/iam/hooks.go +++ b/pkg/registry/apis/iam/hooks.go @@ -100,58 +100,71 @@ func (b *IdentityAccessManagementAPIBuilder) AfterResourcePermissionCreate(obj r return } + // Grab a ticket to write to Zanzana + // This limits the amount of concurrent writes to Zanzana + wait := time.Now() + b.zTickets <- true + hooksWaitHistogram.Observe(time.Since(wait).Seconds()) // Record wait time + rp, ok := obj.(*iamv0.ResourcePermission) if !ok { return } - resource := rp.Spec.Resource - permissions := rp.Spec.Permissions + go func(rp *iamv0.ResourcePermission) { + defer func() { + // Release the ticket after write is done + <-b.zTickets + }() - object := zanzana.NewObjectEntry(toZanzanaType(resource.ApiGroup), resource.ApiGroup, resource.Resource, "", resource.Name) + resource := rp.Spec.Resource + permissions := rp.Spec.Permissions - tuples := make([]*v1.TupleKey, 0, len(permissions)) - for _, p := range permissions { - tuple, err := NewResourceTuple(object, resource, p) - if err != nil { - b.logger.Error("failed to create resource permission tuple", - "namespace", rp.Namespace, - "object", object, - "err", err, - ) + object := zanzana.NewObjectEntry(toZanzanaType(resource.ApiGroup), resource.ApiGroup, resource.Resource, "", resource.Name) - continue + tuples := make([]*v1.TupleKey, 0, len(permissions)) + for _, p := range permissions { + tuple, err := NewResourceTuple(object, resource, p) + if err != nil { + b.logger.Error("failed to create resource permission tuple", + "namespace", rp.Namespace, + "object", object, + "err", err, + ) + + continue + } + tuples = append(tuples, tuple) } - tuples = append(tuples, tuple) - } - // Avoid writing if there are no valid tuples - if len(tuples) == 0 { - b.logger.Warn("no valid tuples to write", "namespace", rp.Namespace, "resource", object) - return - } + // Avoid writing if there are no valid tuples + if len(tuples) == 0 { + b.logger.Warn("no valid tuples to write", "namespace", rp.Namespace, "resource", object) + return + } - b.logger.Debug("writing resource permission to zanzana", - "namespace", rp.Namespace, - "object", object, - "tuplesCnt", len(tuples), - ) - - ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout) - defer cancel() - - err := b.zClient.Write(ctx, &v1.WriteRequest{ - Namespace: rp.Namespace, - Writes: &v1.WriteRequestWrites{ - TupleKeys: tuples, - }, - }) - if err != nil { - b.logger.Error("failed to write resource permission to zanzana", - "err", err, + b.logger.Debug("writing resource permission to zanzana", "namespace", rp.Namespace, "object", object, "tuplesCnt", len(tuples), ) - } + + ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout) + defer cancel() + + err := b.zClient.Write(ctx, &v1.WriteRequest{ + Namespace: rp.Namespace, + Writes: &v1.WriteRequestWrites{ + TupleKeys: tuples, + }, + }) + if err != nil { + b.logger.Error("failed to write resource permission to zanzana", + "err", err, + "namespace", rp.Namespace, + "object", object, + "tuplesCnt", len(tuples), + ) + } + }(rp.DeepCopy()) // Pass a copy of the object } diff --git a/pkg/registry/apis/iam/hooks_test.go b/pkg/registry/apis/iam/hooks_test.go index 8c11f2faa8b..3ee0c7bcd24 100644 --- a/pkg/registry/apis/iam/hooks_test.go +++ b/pkg/registry/apis/iam/hooks_test.go @@ -25,7 +25,8 @@ func (f *FakeZanzanaClient) Write(ctx context.Context, req *v1.WriteRequest) err func TestAfterResourcePermissionCreate(t *testing.T) { b := &IdentityAccessManagementAPIBuilder{ - logger: log.NewNopLogger(), + logger: log.NewNopLogger(), + zTickets: make(chan bool, 1), } t.Run("should create zanzana entries for folder resource permissions", func(t *testing.T) { folderPerm := iamv0.ResourcePermission{ @@ -47,7 +48,7 @@ func TestAfterResourcePermissionCreate(t *testing.T) { require.NotNil(t, req) require.NotNil(t, req.Writes) require.Len(t, req.Writes.TupleKeys, 2) - require.Equal(t, req.Namespace, "org-2") + require.Equal(t, "org-2", req.Namespace) require.Equal( t, req.Writes.TupleKeys[0], @@ -65,6 +66,9 @@ func TestAfterResourcePermissionCreate(t *testing.T) { b.AfterResourcePermissionCreate(&folderPerm, nil) }) + // Wait for the ticket to be released + <-b.zTickets + t.Run("should create zanzana entries for dashboard resource permissions", func(t *testing.T) { dashPerm := iamv0.ResourcePermission{ ObjectMeta: metav1.ObjectMeta{ @@ -87,7 +91,7 @@ func TestAfterResourcePermissionCreate(t *testing.T) { require.NotNil(t, req) require.NotNil(t, req.Writes) require.Len(t, req.Writes.TupleKeys, 2) - require.Equal(t, req.Namespace, "default") + require.Equal(t, "default", req.Namespace) tuple1 := req.Writes.TupleKeys[0] require.NotNil(t, tuple1.Condition) diff --git a/pkg/registry/apis/iam/metrics.go b/pkg/registry/apis/iam/metrics.go new file mode 100644 index 00000000000..6c25a8746f7 --- /dev/null +++ b/pkg/registry/apis/iam/metrics.go @@ -0,0 +1,32 @@ +package iam + +import ( + "sync" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/prometheus/client_golang/prometheus" +) + +const ( + metricsNamespace = "iam" + metricsSubSystem = "apiserver" +) + +var ( + registerOnce sync.Once + hooksWaitHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{ + Namespace: metricsNamespace, + Subsystem: metricsSubSystem, + Name: "hooks_wait_duration_seconds", + Help: "Time spent in the hooks waiting for a ticket to start processing", + Buckets: prometheus.ExponentialBuckets(0.001, 2, 5), // 1ms to ~16s + }) +) + +func registerMetrics(reg prometheus.Registerer) { + registerOnce.Do(func() { + if err := reg.Register(hooksWaitHistogram); err != nil { + log.New("iam.apis").Warn("failed to register iam apiserver metrics", "error", err) + } + }) +} diff --git a/pkg/registry/apis/iam/models.go b/pkg/registry/apis/iam/models.go index 49e1662263a..0ab7869c30a 100644 --- a/pkg/registry/apis/iam/models.go +++ b/pkg/registry/apis/iam/models.go @@ -53,6 +53,8 @@ type IdentityAccessManagementAPIBuilder struct { // - permissions // - assignments zClient zanzana.Client + // Buffered channel to limit the amount of concurrent writes to Zanzana + zTickets chan bool reg prometheus.Registerer logger log.Logger diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index e5c1d2b98f2..9ab61ff8a1c 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -46,6 +46,8 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/resource" ) +const MaxConcurrentZanzanaWrites = 20 + func RegisterAPIService( features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar, @@ -63,6 +65,7 @@ func RegisterAPIService( store := legacy.NewLegacySQLStores(dbProvider) legacyAccessClient := newLegacyAccessClient(ac, store) authorizer := newIAMAuthorizer(accessClient, legacyAccessClient) + registerMetrics(reg) builder := &IdentityAccessManagementAPIBuilder{ store: store, @@ -75,21 +78,19 @@ func RegisterAPIService( legacyAccessClient: legacyAccessClient, accessClient: accessClient, zClient: zClient, + zTickets: make(chan bool, MaxConcurrentZanzanaWrites), display: user.NewLegacyDisplayREST(store), reg: reg, logger: log.New("iam.apis"), features: features, - // enableAuthZApis: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzApis), - // enableResourcePermissionApis: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzResourcePermissionApis), - // enableAuthnMutation: features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthnMutation), - enableDualWriter: true, + enableDualWriter: true, } apiregistration.RegisterAPI(builder) return builder, nil } -// TODO zClient, reg +// TODO zClient, zTickets, reg func NewAPIService( accessClient types.AccessClient, dbProvider legacysql.LegacyDatabaseProvider, From 83e24410340eb5b4b8190d77231eb6e8b4a4f8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Jamr=C3=B3z?= <pm.jamroz@gmail.com> Date: Thu, 16 Oct 2025 08:35:48 +0200 Subject: [PATCH 67/75] Span Details: Wrap label values (#112413) fix: Wrap label values --- .../explore/TraceView/components/common/LabeledList.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/app/features/explore/TraceView/components/common/LabeledList.tsx b/public/app/features/explore/TraceView/components/common/LabeledList.tsx index b45e49b4cfa..1c09a2dda25 100644 --- a/public/app/features/explore/TraceView/components/common/LabeledList.tsx +++ b/public/app/features/explore/TraceView/components/common/LabeledList.tsx @@ -55,6 +55,8 @@ const getStyles = (divider: boolean) => (theme: GrafanaTheme2) => { LabeledListValue: css({ label: 'LabeledListValue', marginRight: divider ? undefined : '0.55rem', + wordWrap: 'break-word', + wordBreak: 'break-all', }), LabeledListIcon: css({ label: 'LabeledListIcon', From b1af9ca2fa9bec5192d578e97dd9b3e3b3a11329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Jamr=C3=B3z?= <pm.jamroz@gmail.com> Date: Thu, 16 Oct 2025 09:02:56 +0200 Subject: [PATCH 68/75] Span Details: Bring back span id to span details (#112411) * fix: Bring back span id to span details * Fix passing dom props * Update mock --- .../SpanDetail/index.test.tsx | 1 + .../TraceTimelineViewer/SpanDetail/index.tsx | 47 +++++++++++++++++-- .../SpanDetailRow.test.tsx | 2 +- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx index 9b382bff768..f65d347c241 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.test.tsx @@ -253,6 +253,7 @@ describe('<SpanDetail>', () => { it('renders deep link URL', () => { render(<SpanDetail {...(props as unknown as SpanDetailProps)} />); expect(screen.getByTestId('share-span-button')).toBeInTheDocument(); + expect(screen.getByText('test-spanID')).toBeInTheDocument(); }); it('renders the flame graph', async () => { diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx index 4550f310064..19ddc677310 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/index.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import { SpanStatusCode } from '@opentelemetry/api'; import { useCallback, useMemo } from 'react'; @@ -33,7 +33,7 @@ import { t } from '@grafana/i18n'; import { TraceToProfilesOptions } from '@grafana/o11y-ds-frontend'; import { usePluginLinks } from '@grafana/runtime'; import { TimeZone } from '@grafana/schema'; -import { useStyles2 } from '@grafana/ui'; +import { Icon, useStyles2 } from '@grafana/ui'; import { pyroscopeProfileIdTagKey } from '../../../createSpanLink'; import { autoColor } from '../../Theme'; @@ -174,6 +174,23 @@ const getStyles = (theme: GrafanaTheme2) => { gap: '10px', marginBottom: theme.spacing(2), }), + debugInfo: css({ + label: 'debugInfo', + display: 'block', + letterSpacing: '0.25px', + margin: '0.5em 0 -0.75em', + textAlign: 'right', + }), + debugLabel: css({ + label: 'debugLabel', + '&::before': { + color: autoColor(theme, '#bbb'), + content: 'attr(data-label)', + }, + }), + LinkIcon: css({ + fontSize: '1.5em', + }), }; }; @@ -350,7 +367,7 @@ export default function SpanDetail(props: SpanDetailProps) { app, }); - const focusSpanLink = createFocusSpanLink(traceID, spanID); + const { interpolatedParams, ...focusSpanLink } = createFocusSpanLink(traceID, spanID); const resourceLinksGetter = useResourceAttributesExtensionLinks({ process, spanTags: tags, @@ -455,6 +472,30 @@ export default function SpanDetail(props: SpanDetailProps) { traceName={traceName} /> )} + + <small className={styles.debugInfo}> + {/* TODO: fix keyboard a11y */} + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} + <a + {...focusSpanLink} + onClick={(e) => { + // click handling logic copied from react router: + // https://github.com/remix-run/react-router/blob/997b4d67e506d39ac6571cb369d6d2d6b3dda557/packages/react-router-dom/index.tsx#L392-L394s + if ( + focusSpanLink.onClick && + e.button === 0 && // Ignore everything but left clicks + (!e.currentTarget.target || e.currentTarget.target === '_self') && // Let browser handle "target=_blank" etc. + !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) // Ignore clicks with modifier keys + ) { + e.preventDefault(); + focusSpanLink.onClick(e); + } + }} + > + <Icon name={'link'} className={cx(alignIcon, styles.LinkIcon)}></Icon> + </a> + <span className={styles.debugLabel} data-label="SpanID:" /> {spanID} + </small> </div> </div> ); diff --git a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.test.tsx b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.test.tsx index 629e6da37c5..c4c94b9b102 100644 --- a/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.test.tsx +++ b/public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.test.tsx @@ -40,7 +40,7 @@ const setup = (propOverrides?: SpanDetailRowProps) => { logItemToggle: jest.fn(), logsToggle: jest.fn(), processToggle: jest.fn(), - createFocusSpanLink: jest.fn(), + createFocusSpanLink: jest.fn().mockReturnValue({}), hoverIndentGuideIds: new Map(), span: testSpan, tagsToggle: jest.fn(), From 62995971a21e93143e36ebbccaca5570afc83e0e Mon Sep 17 00:00:00 2001 From: Anna Urbiztondo <anna.urbiztondo@grafana.com> Date: Thu, 16 Oct 2025 10:45:23 +0200 Subject: [PATCH 69/75] Docs: Manage multi-team access (#111824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Draft * Fixes * Prettier * Fix? * Fix? * Links * Paths * Link * Images * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * Link * URIs * add an example ref URI * More refs * Prettier * adding ref URIs Updated links to use reference format for drilldown and add-data-source sections. * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> * LBAC * Prettier * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Sarah Constant <sarahleejane@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Sarah Constant <sarahleejane@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Sarah Constant <sarahleejane@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Sarah Constant <sarahleejane@users.noreply.github.com> * Update docs/sources/setup-grafana/configure-security/manage-single-access.md Co-authored-by: Sarah Constant <sarahleejane@users.noreply.github.com> * Edit --------- Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com> Co-authored-by: Irene Rodríguez <irene.rodriguez@grafana.com> Co-authored-by: Sarah Constant <sarahleejane@users.noreply.github.com> --- .../manage-single-access.md | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 docs/sources/setup-grafana/configure-security/manage-single-access.md diff --git a/docs/sources/setup-grafana/configure-security/manage-single-access.md b/docs/sources/setup-grafana/configure-security/manage-single-access.md new file mode 100644 index 00000000000..1ca84d68960 --- /dev/null +++ b/docs/sources/setup-grafana/configure-security/manage-single-access.md @@ -0,0 +1,158 @@ +--- +aliases: + - ../../enterprise/manage-single-access/ +description: Manage multi-team access in a single Grafana instance +keywords: + - grafana + - rbac + - lbac + - auth + - access + - teams +labels: + products: + - cloud + - enterprise +title: Manage multi-team access in a single Grafana instance +weight: 1200 +refs: + create-folder: + - pattern: /docs/grafana/ + destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/manage-dashboards/#create-a-dashboard-folder + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/dashboards/manage-dashboards/#create-a-dashboard-folder + rbac: + - pattern: /docs/grafana/ + destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/security-and-account-management/authentication-and-permissions/access-control + rbac-assign: + - pattern: /docs/grafana/ + destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/assign-rbac-roles + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/security-and-account-management/authentication-and-permissions/access-control/assign-rbac-roles + rbac-fixed: + - pattern: /docs/grafana/ + destination: /docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/rbac-fixed-basic-role-definitions/#fixed-role-definitions + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/security-and-account-management/authentication-and-permissions/access-control/rbac-fixed-basic-role-definitions/#fixed-role-definitions + drilldown: + - pattern: /docs/grafana/ + destination: /docs/grafana/<GRAFANA_VERSION/explore/simplified-exploration/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/simplified-exploration/ + add-data-source: + - pattern: /docs/grafana/ + destination: /docs/grafana/<GRAFANA_VERSION/datasources/#add-a-data-source + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/#add-a-data-source + lbac: + - pattern: /docs/grafana/ + destination: /docs/grafana/<GRAFANA_VERSION/administration/data-source-management/teamlbac + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/security-and-account-management/authentication-and-permissions/access-policies/label-access-policies +--- + +# Manage multi-team access in a single Grafana instance + +If your organization has multiple teams using Grafana, you can use a single Grafana Enterprise deployment or a single Grafana Cloud stack to manage access across teams using roles and folders. This approach reduces complexity, simplifies identity and access management, and facilitates cross-team collaboration. + +## Benefits + +By using a single Grafana instance to manage access, you can: + +- Implement a unified SSO, establishing clear permissions. +- Reduce setup and maintenance work, avoiding multi-stack complexity. +- Centralize plugin configuration and management. +- Ensure teams can access the right dashboards and data, avoiding stepping on or overwriting each other’s work. +- Enable collaboration across teams. Teams are not isolated in silos and can discover and collaborate with each other’s work. +- Optimize resource management. With shared spaces, like an “Everyone” folder, you can publish executive dashboards or cross-team metrics that all groups can benefit from, without duplicating it across stacks. + +## Example: Three teams, one stack + +Consider the following setup of three teams: + +- Team A builds product features and needs autonomy with their own dashboards and data sources. +- Team B handles data engineering and needs autonomy with their own dashboards. +- Team C is the observability team and the admins of the Grafana stack. + +Follow these suggested steps to structure, configure, and set permissions to access data in your Grafana instance: + +1. [Before you begin](#before-you-begin) +1. [Create teams and configure user access](#create-teams-and-configure-user-access) +1. [Design a folder structure to match your access needs](#design-a-folder-structure-to-match-your-access-needs) +1. [Configure data access based on each team’s requirements](#configure-data-access-based-on-team-requirements) +1. [Scale access management with Terraform and SSO](#scale-access-management-with-terraform-and-sso) + +### Before you begin + +For more information on how to install a Grafana instance: + +- If you’re using self-managed Grafana Enterprise, refer to [Configure Grafana](../../configure-grafana/). +- If you’re using Grafana Cloud, refer to [Your Grafana Cloud stack](https://grafana.com/docs/grafana-cloud/security-and-account-management/cloud-stacks). + +{{< admonition type="note" >}} +For guidance on when to use one stack versus multiple, refer to [Stack architecture guidance](https://grafana.com/docs/grafana-cloud/security-and-account-management/cloud-stacks/stack-architecture-guidance/). +{{< /admonition >}} + +### Create teams and configure user access + +After you’ve deployed your Grafana instance: + +- To follow the example in this doc, create three [Grafana Teams](../../../administration/team-management/configure-grafana-teams/#create-a-grafana-team) and add them to the Grafana instance. +- Determine the [RBAC](ref:rbac) strategy for your organization. RBAC extends default Grafana roles, provides more granular access rights, and simplifies how to grant, modify, or revoke user access to Grafana resources, such as users and reports. +- Assign each user to the [relevant team](../../../administration/user-management/manage-org-users/). By default [new users](../../configure-grafana/#auto_assign_org) are granted the **Viewer** role. +- Assign the [**Admin** role](ref:rbac-assign) to Team C so that they can manage all resources in the instance. + +### Design a folder structure to match your access needs + +To design a [folder](ref:create-folder) setup that helps users quickly understand where to go, what they can access, and what they can manage: + +- Create an “Everyone” folder for shared items that all teams can manage, and grant teams Admin access to that folder. +- For each team, create a folder that they can manage and grant them the `fixed:teams:read` [fixed role](ref:rbac-assign). This means they can share items in their team folder with other teams, to encourage collaboration and learning from each other. +- For Team C, create an “Admins” folder for sensitive content only Admins can access. +- Optionally, create a personal folder for each team member so that they can work on draft content before moving it into their team folder when ready. + +{{< figure src="/media/docs/grafana/oac/AccessTeams01.png" max-width="750px" alt="Teams and folders in the stack, and the related admin permissions Team A and Team B have been granted" >}} + +### Configure data access based on team requirements + +Next, focus on how teams interact with data to decide further access needs. + +#### Shared baseline data access + +Grant the `datasources:explorer` fixed role to all teams so they can use the [Drilldown apps](ref:drilldown) for easily exploring data sources. + +However, you may need to protect data in shared resources. For example, all teams can be forwarding metrics to a shared [data source](ref:add-data-source), but not everyone needs to see all of the data. In this case, grant each team query access to the data relevant for them, based on [label based access controls (LBAC) per team](ref:lbac). This way, you’ll maintain a central observability pipeline but still preserve data separation. + +#### Autonomous team data management + +If any of your teams, Team A for example, need to build and manage their own data sources for product-specific use cases, grant the `datasources:creator` fixed role so they can create and manage their own data sources independently. + +{{< figure src="/media/docs/grafana/oac/AccessTeams02.png" max-width="750px" alt="Teams and data sources in the stack, and the related permissions Team A and Team B have been granted" >}} + +#### Resources at an instance level + +Some Grafana resources, such as service accounts, alert contact points, [Fleet Management collectors](https://grafana.com/docs/grafana-cloud/send-data/fleet-management/), and other feature resources, are not linked to teams but are managed at the stack level. For these type of resources, assign fixed roles to teams carefully. + +For example, users working in [Frontend Observability](https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/) need a writer fixed role so that they can create and manage services. + +{{< figure src="/media/docs/grafana/oac/AccessTeams03.png" max-width="750px" alt="Grafana Cloud Frontend Observability resources in the stack, and the related permissions Team A have been granted" >}} + +### Scale access management with Terraform and SSO + +After you've made sure the model is working, you can codify it. + +You can add any new users to your Grafana instance with an Identity Provider through [SCIM](../../configure-security/configure-scim-provisioning/). Use [role sync](../../configure-security/configure-authentication/saml/configure-saml-team-role-mapping/#configure-role-sync-for-saml) to automatically assign users the correct basic role (Viewer, Editor, or Admin) based on their mapped attributes in the IdP.. + +You can also use Terraform to provision teams their folders, fixed roles, and shared data source LBAC rules. For example, if you need to add a new team (Team D), you only need to add the new team to Grafana and run the Terraform script, which will automatically set them up to start using Grafana. + +{{< figure src="/media/docs/grafana/oac/AccessTeams04.png" max-width="750px" alt="Add new Team D from Okta and automate the rest of their IAM setup using Terraform" >}} + +## Other resources + +Read on to learn more about access management: + +- The [Least privilege custom role explainer](https://grafana.com/blog/2024/09/10/grafana-access-management-how-to-use-teams-for-seamless-user-and-permission-management/) blog walks through how to design roles that keep things simple and safe, so your users have just the access they need. +- See the [LBAC for metrics data sources](https://www.youtube.com/watch?v=gj27qKPSVsM) demo to learn how you can give every team a clear view of their own data while still benefiting from a shared pipeline. +- The [Introducing SCIM](https://grafana.com/blog/2025/05/14/introducing-scim-provisioning-in-grafana-enterprise-grade-user-management-made-simple/) post covers how to connect Grafana to your identity provider, making it easy to bring new users on board and keep permissions in sync as your organization grows. From 3f9bf130c789d47ef31d2925520ffeff7dc8c26b Mon Sep 17 00:00:00 2001 From: Mihai Doarna <mihai.doarna@grafana.com> Date: Thu, 16 Oct 2025 11:56:08 +0300 Subject: [PATCH 70/75] IAM: Move the teambinding code to a separate dir (#112405) * move the teambinding code to a separate dir * validate pagination limit to fix codeql error * supress codeql warning * convert int to int64 --- pkg/registry/apis/iam/legacy/team.go | 294 ----------------- pkg/registry/apis/iam/legacy/team_binding.go | 306 ++++++++++++++++++ pkg/registry/apis/iam/register.go | 5 +- pkg/registry/apis/iam/team/rest_members.go | 2 +- pkg/registry/apis/iam/team/validate.go | 21 -- pkg/registry/apis/iam/team/validate_test.go | 110 ------- .../store_binding.go => teambinding/store.go} | 21 +- pkg/registry/apis/iam/teambinding/validate.go | 31 ++ .../apis/iam/teambinding/validate_test.go | 123 +++++++ 9 files changed, 470 insertions(+), 443 deletions(-) create mode 100644 pkg/registry/apis/iam/legacy/team_binding.go rename pkg/registry/apis/iam/{team/store_binding.go => teambinding/store.go} (92%) create mode 100644 pkg/registry/apis/iam/teambinding/validate.go create mode 100644 pkg/registry/apis/iam/teambinding/validate_test.go diff --git a/pkg/registry/apis/iam/legacy/team.go b/pkg/registry/apis/iam/legacy/team.go index 191cc02cbee..e1dc0909869 100644 --- a/pkg/registry/apis/iam/legacy/team.go +++ b/pkg/registry/apis/iam/legacy/team.go @@ -2,7 +2,6 @@ package legacy import ( "context" - "database/sql" "errors" "fmt" "time" @@ -412,296 +411,3 @@ func (s *legacySQLStore) DeleteTeam(ctx context.Context, ns claims.NamespaceInfo return nil }) } - -type ListTeamBindingsQuery struct { - TeamID int64 - UserID int64 - OrgID int64 - Pagination common.Pagination -} - -type ListTeamBindingsResult struct { - Bindings []TeamMember - Continue int64 - RV int64 -} - -type TeamMember struct { - ID int64 - TeamID int64 - TeamUID string - UserID int64 - UserUID string - OrgID int64 - Name string - Email string - Username string - External bool - Updated time.Time - Created time.Time - Permission team.PermissionType -} - -func (m TeamMember) MemberID() string { - return claims.NewTypeID(claims.TypeUser, m.UserUID) -} - -var sqlQueryTeamBindingsTemplate = mustTemplate("team_bindings_query.sql") - -type listTeamBindingsQuery struct { - sqltemplate.SQLTemplate - Query *ListTeamBindingsQuery - UserTable string - TeamTable string - TeamMemberTable string -} - -func (r listTeamBindingsQuery) Validate() error { - return nil // TODO -} - -func newListTeamBindings(sql *legacysql.LegacyDatabaseHelper, q *ListTeamBindingsQuery) listTeamBindingsQuery { - return listTeamBindingsQuery{ - SQLTemplate: sqltemplate.New(sql.DialectForDriver()), - UserTable: sql.Table("user"), - TeamTable: sql.Table("team"), - TeamMemberTable: sql.Table("team_member"), - Query: q, - } -} - -// ListTeamsBindings implements LegacyIdentityStore. -func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error) { - // for continue - query.Pagination.Limit += 1 - query.OrgID = ns.OrgID - if query.OrgID == 0 { - return nil, fmt.Errorf("expected non zero orgID") - } - - sql, err := s.sql(ctx) - if err != nil { - return nil, err - } - - req := newListTeamBindings(sql, &query) - q, err := sqltemplate.Execute(sqlQueryTeamBindingsTemplate, req) - if err != nil { - return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamBindingsTemplate.Name(), err) - } - - rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) - defer func() { - if rows != nil { - _ = rows.Close() - } - }() - - if err != nil { - return nil, err - } - - res := &ListTeamBindingsResult{ - Bindings: make([]TeamMember, 0, int(query.Pagination.Limit)), - } - - var lastID int64 - - for rows.Next() { - m := TeamMember{} - err = rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Created, &m.Updated, &m.Permission, &m.External) - if err != nil { - return res, err - } - - res.Bindings = append(res.Bindings, m) - - lastID = m.ID - - if len(res.Bindings) >= int(query.Pagination.Limit)-1 { - res.Continue = lastID - } - } - - return res, err -} - -type CreateTeamMemberCommand struct { - TeamID int64 - TeamUID string - UserID int64 - UserUID string - OrgID int64 - Created DBTime - Updated DBTime - External bool - Permission team.PermissionType -} - -type CreateTeamMemberResult struct { - TeamMember TeamMember -} - -var sqlCreateTeamMemberQuery = mustTemplate("create_team_member_query.sql") - -func newCreateTeamMember(sql *legacysql.LegacyDatabaseHelper, cmd *CreateTeamMemberCommand) createTeamMemberQuery { - return createTeamMemberQuery{ - SQLTemplate: sqltemplate.New(sql.DialectForDriver()), - TeamMemberTable: sql.Table("team_member"), - Command: cmd, - } -} - -type createTeamMemberQuery struct { - sqltemplate.SQLTemplate - TeamMemberTable string - Command *CreateTeamMemberCommand -} - -func (r createTeamMemberQuery) Validate() error { - return nil -} - -func (s *legacySQLStore) CreateTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd CreateTeamMemberCommand) (*CreateTeamMemberResult, error) { - now := time.Now().UTC() - cmd.Created = NewDBTime(now) - cmd.Updated = NewDBTime(now) - cmd.OrgID = ns.OrgID - - if cmd.OrgID == 0 { - return nil, fmt.Errorf("expected non zero org id") - } - - sql, err := s.sql(ctx) - if err != nil { - return nil, err - } - - req := newCreateTeamMember(sql, &cmd) - - var createdTeamMember TeamMember - err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error { - teamMemberQuery, err := sqltemplate.Execute(sqlCreateTeamMemberQuery, req) - if err != nil { - return fmt.Errorf("failed to execute team member template %q: %w", sqlCreateTeamMemberQuery.Name(), err) - } - - teamMemberID, err := st.ExecWithReturningId(ctx, teamMemberQuery, req.GetArgs()...) - if err != nil { - return fmt.Errorf("failed to create team member: %w", err) - } - - createdTeamMember = TeamMember{ - ID: teamMemberID, - TeamID: cmd.TeamID, - TeamUID: cmd.TeamUID, - UserID: cmd.UserID, - UserUID: cmd.UserUID, - OrgID: cmd.OrgID, - Created: cmd.Created.Time, - Updated: cmd.Updated.Time, - External: cmd.External, - Permission: cmd.Permission, - } - - return nil - }) - - if err != nil { - return nil, err - } - - return &CreateTeamMemberResult{TeamMember: createdTeamMember}, nil -} - -type ListTeamMembersQuery struct { - UID string - OrgID int64 - Pagination common.Pagination -} - -type ListTeamMembersResult struct { - Continue int64 - Members []TeamMember -} - -// Templates. -var sqlQueryTeamMembersTemplate = mustTemplate("team_members_query.sql") - -type listTeamMembersQuery struct { - sqltemplate.SQLTemplate - Query *ListTeamMembersQuery - UserTable string - TeamTable string - TeamMemberTable string -} - -func (r listTeamMembersQuery) Validate() error { - return nil // TODO -} - -func newListTeamMembers(sql *legacysql.LegacyDatabaseHelper, q *ListTeamMembersQuery) listTeamMembersQuery { - return listTeamMembersQuery{ - SQLTemplate: sqltemplate.New(sql.DialectForDriver()), - UserTable: sql.Table("user"), - TeamTable: sql.Table("team"), - TeamMemberTable: sql.Table("team_member"), - Query: q, - } -} - -// ListTeamMembers implements LegacyIdentityStore. -func (s *legacySQLStore) ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error) { - query.Pagination.Limit += 1 - query.OrgID = ns.OrgID - if query.OrgID == 0 { - return nil, fmt.Errorf("expected non zero org id") - } - - sql, err := s.sql(ctx) - if err != nil { - return nil, err - } - - req := newListTeamMembers(sql, &query) - q, err := sqltemplate.Execute(sqlQueryTeamMembersTemplate, req) - if err != nil { - return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err) - } - - rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) - defer func() { - if rows != nil { - _ = rows.Close() - } - }() - - if err != nil { - return nil, err - } - - res := &ListTeamMembersResult{} - var lastID int64 - for rows.Next() { - m, err := scanMember(rows) - if err != nil { - return nil, err - } - - lastID = m.ID - res.Members = append(res.Members, m) - if len(res.Members) > int(query.Pagination.Limit)-1 { - res.Continue = lastID - res.Members = res.Members[0 : len(res.Members)-1] - break - } - } - - return res, err -} - -func scanMember(rows *sql.Rows) (TeamMember, error) { - m := TeamMember{} - err := rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Name, &m.Email, &m.Username, &m.External, &m.Created, &m.Updated, &m.Permission) - return m, err -} diff --git a/pkg/registry/apis/iam/legacy/team_binding.go b/pkg/registry/apis/iam/legacy/team_binding.go new file mode 100644 index 00000000000..3cf13e779e6 --- /dev/null +++ b/pkg/registry/apis/iam/legacy/team_binding.go @@ -0,0 +1,306 @@ +package legacy + +import ( + "context" + "database/sql" + "fmt" + "time" + + claims "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/registry/apis/iam/common" + "github.com/grafana/grafana/pkg/services/sqlstore/session" + "github.com/grafana/grafana/pkg/services/team" + "github.com/grafana/grafana/pkg/storage/legacysql" + "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" +) + +type ListTeamBindingsQuery struct { + TeamID int64 + UserID int64 + OrgID int64 + Pagination common.Pagination +} + +type ListTeamBindingsResult struct { + Bindings []TeamMember + Continue int64 + RV int64 +} + +type TeamMember struct { + ID int64 + TeamID int64 + TeamUID string + UserID int64 + UserUID string + OrgID int64 + Name string + Email string + Username string + External bool + Updated time.Time + Created time.Time + Permission team.PermissionType +} + +func (m TeamMember) MemberID() string { + return claims.NewTypeID(claims.TypeUser, m.UserUID) +} + +var sqlQueryTeamBindingsTemplate = mustTemplate("team_bindings_query.sql") + +type listTeamBindingsQuery struct { + sqltemplate.SQLTemplate + Query *ListTeamBindingsQuery + UserTable string + TeamTable string + TeamMemberTable string +} + +func (r listTeamBindingsQuery) Validate() error { + return nil // TODO +} + +func newListTeamBindings(sql *legacysql.LegacyDatabaseHelper, q *ListTeamBindingsQuery) listTeamBindingsQuery { + return listTeamBindingsQuery{ + SQLTemplate: sqltemplate.New(sql.DialectForDriver()), + UserTable: sql.Table("user"), + TeamTable: sql.Table("team"), + TeamMemberTable: sql.Table("team_member"), + Query: q, + } +} + +func (s *legacySQLStore) ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error) { + // for continue + query.Pagination.Limit += 1 + query.OrgID = ns.OrgID + if query.OrgID == 0 { + return nil, fmt.Errorf("expected non zero orgID") + } + + sql, err := s.sql(ctx) + if err != nil { + return nil, err + } + + req := newListTeamBindings(sql, &query) + q, err := sqltemplate.Execute(sqlQueryTeamBindingsTemplate, req) + if err != nil { + return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamBindingsTemplate.Name(), err) + } + + rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) + defer func() { + if rows != nil { + _ = rows.Close() + } + }() + + if err != nil { + return nil, err + } + + res := &ListTeamBindingsResult{ + Bindings: make([]TeamMember, 0, int(query.Pagination.Limit)), + } + + var lastID int64 + + for rows.Next() { + m := TeamMember{} + err = rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Created, &m.Updated, &m.Permission, &m.External) + if err != nil { + return res, err + } + + res.Bindings = append(res.Bindings, m) + + lastID = m.ID + + if len(res.Bindings) >= int(query.Pagination.Limit)-1 { + res.Continue = lastID + } + } + + return res, err +} + +type CreateTeamMemberCommand struct { + TeamID int64 + TeamUID string + UserID int64 + UserUID string + OrgID int64 + Created DBTime + Updated DBTime + External bool + Permission team.PermissionType +} + +type CreateTeamMemberResult struct { + TeamMember TeamMember +} + +var sqlCreateTeamMemberQuery = mustTemplate("create_team_member_query.sql") + +func newCreateTeamMember(sql *legacysql.LegacyDatabaseHelper, cmd *CreateTeamMemberCommand) createTeamMemberQuery { + return createTeamMemberQuery{ + SQLTemplate: sqltemplate.New(sql.DialectForDriver()), + TeamMemberTable: sql.Table("team_member"), + Command: cmd, + } +} + +type createTeamMemberQuery struct { + sqltemplate.SQLTemplate + TeamMemberTable string + Command *CreateTeamMemberCommand +} + +func (r createTeamMemberQuery) Validate() error { + return nil +} + +func (s *legacySQLStore) CreateTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd CreateTeamMemberCommand) (*CreateTeamMemberResult, error) { + now := time.Now().UTC() + cmd.Created = NewDBTime(now) + cmd.Updated = NewDBTime(now) + cmd.OrgID = ns.OrgID + + if cmd.OrgID == 0 { + return nil, fmt.Errorf("expected non zero org id") + } + + sql, err := s.sql(ctx) + if err != nil { + return nil, err + } + + req := newCreateTeamMember(sql, &cmd) + + var createdTeamMember TeamMember + err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error { + teamMemberQuery, err := sqltemplate.Execute(sqlCreateTeamMemberQuery, req) + if err != nil { + return fmt.Errorf("failed to execute team member template %q: %w", sqlCreateTeamMemberQuery.Name(), err) + } + + teamMemberID, err := st.ExecWithReturningId(ctx, teamMemberQuery, req.GetArgs()...) + if err != nil { + return fmt.Errorf("failed to create team member: %w", err) + } + + createdTeamMember = TeamMember{ + ID: teamMemberID, + TeamID: cmd.TeamID, + TeamUID: cmd.TeamUID, + UserID: cmd.UserID, + UserUID: cmd.UserUID, + OrgID: cmd.OrgID, + Created: cmd.Created.Time, + Updated: cmd.Updated.Time, + External: cmd.External, + Permission: cmd.Permission, + } + + return nil + }) + + if err != nil { + return nil, err + } + + return &CreateTeamMemberResult{TeamMember: createdTeamMember}, nil +} + +type ListTeamMembersQuery struct { + UID string + OrgID int64 + Pagination common.Pagination +} + +type ListTeamMembersResult struct { + Continue int64 + Members []TeamMember +} + +var sqlQueryTeamMembersTemplate = mustTemplate("team_members_query.sql") + +type listTeamMembersQuery struct { + sqltemplate.SQLTemplate + Query *ListTeamMembersQuery + UserTable string + TeamTable string + TeamMemberTable string +} + +func (r listTeamMembersQuery) Validate() error { + return nil // TODO +} + +func newListTeamMembers(sql *legacysql.LegacyDatabaseHelper, q *ListTeamMembersQuery) listTeamMembersQuery { + return listTeamMembersQuery{ + SQLTemplate: sqltemplate.New(sql.DialectForDriver()), + UserTable: sql.Table("user"), + TeamTable: sql.Table("team"), + TeamMemberTable: sql.Table("team_member"), + Query: q, + } +} + +func (s *legacySQLStore) ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error) { + query.Pagination.Limit += 1 + query.OrgID = ns.OrgID + if query.OrgID == 0 { + return nil, fmt.Errorf("expected non zero org id") + } + + sql, err := s.sql(ctx) + if err != nil { + return nil, err + } + + req := newListTeamMembers(sql, &query) + q, err := sqltemplate.Execute(sqlQueryTeamMembersTemplate, req) + if err != nil { + return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err) + } + + rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) + defer func() { + if rows != nil { + _ = rows.Close() + } + }() + + if err != nil { + return nil, err + } + + res := &ListTeamMembersResult{} + var lastID int64 + for rows.Next() { + m, err := scanMember(rows) + if err != nil { + return nil, err + } + + lastID = m.ID + res.Members = append(res.Members, m) + + if int64(len(res.Members)) > query.Pagination.Limit-1 { + res.Continue = lastID + res.Members = res.Members[0 : len(res.Members)-1] + break + } + } + + return res, err +} + +func scanMember(rows *sql.Rows) (TeamMember, error) { + m := TeamMember{} + err := rows.Scan(&m.ID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Name, &m.Email, &m.Username, &m.External, &m.Created, &m.Updated, &m.Permission) + return m, err +} diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 9ab61ff8a1c..16c7c3fb160 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -34,6 +34,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/iam/serviceaccount" "github.com/grafana/grafana/pkg/registry/apis/iam/sso" "github.com/grafana/grafana/pkg/registry/apis/iam/team" + "github.com/grafana/grafana/pkg/registry/apis/iam/teambinding" "github.com/grafana/grafana/pkg/registry/apis/iam/user" "github.com/grafana/grafana/pkg/services/accesscontrol" gfauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" @@ -192,7 +193,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge } teamBindingResource := iamv0.TeamBindingResourceInfo - teamBindingLegacyStore := team.NewLegacyBindingStore(b.store, enableAuthnMutation) + teamBindingLegacyStore := teambinding.NewLegacyBindingStore(b.store, enableAuthnMutation) storage[teamBindingResource.StoragePath()] = teamBindingLegacyStore if b.enableDualWriter { @@ -377,7 +378,7 @@ func (b *IdentityAccessManagementAPIBuilder) Validate(ctx context.Context, a adm case *iamv0.Team: return team.ValidateOnCreate(ctx, typedObj) case *iamv0.TeamBinding: - return team.ValidateOnBindingCreate(ctx, typedObj) + return teambinding.ValidateOnCreate(ctx, typedObj) case *iamv0.ResourcePermission: return resourcepermission.ValidateCreateAndUpdateInput(ctx, typedObj) } diff --git a/pkg/registry/apis/iam/team/rest_members.go b/pkg/registry/apis/iam/team/rest_members.go index edac4cc8f59..6659823b9fa 100644 --- a/pkg/registry/apis/iam/team/rest_members.go +++ b/pkg/registry/apis/iam/team/rest_members.go @@ -107,6 +107,6 @@ func mapToTeamMember(m legacy.TeamMember) iamv0.TeamMember { InternalID: m.UserID, }, External: m.External, - Permission: mapPermisson(m.Permission), + Permission: common.MapUserTeamPermission(m.Permission), } } diff --git a/pkg/registry/apis/iam/team/validate.go b/pkg/registry/apis/iam/team/validate.go index 6859310e402..97777cb3c4e 100644 --- a/pkg/registry/apis/iam/team/validate.go +++ b/pkg/registry/apis/iam/team/validate.go @@ -55,24 +55,3 @@ func ValidateOnUpdate(ctx context.Context, obj, old *iamv0alpha1.Team) error { return nil } - -func ValidateOnBindingCreate(ctx context.Context, obj *iamv0alpha1.TeamBinding) error { - _, err := identity.GetRequester(ctx) - if err != nil { - return apierrors.NewUnauthorized("no identity found") - } - - if obj.Spec.Permission != iamv0alpha1.TeamBindingTeamPermissionAdmin && obj.Spec.Permission != iamv0alpha1.TeamBindingTeamPermissionMember { - return apierrors.NewBadRequest("invalid permission") - } - - if obj.Spec.Subject.Name == "" { - return apierrors.NewBadRequest("subject is required") - } - - if obj.Spec.TeamRef.Name == "" { - return apierrors.NewBadRequest("teamRef is required") - } - - return nil -} diff --git a/pkg/registry/apis/iam/team/validate_test.go b/pkg/registry/apis/iam/team/validate_test.go index e4a7e0bf53b..1093a01f57d 100644 --- a/pkg/registry/apis/iam/team/validate_test.go +++ b/pkg/registry/apis/iam/team/validate_test.go @@ -325,113 +325,3 @@ func TestValidateOnUpdate(t *testing.T) { }) } } - -func TestValidateOnBindingCreate(t *testing.T) { - tests := []struct { - name string - requester *identity.StaticRequester - obj *iamv0alpha1.TeamBinding - want error - }{ - { - name: "valid team binding create", - requester: &identity.StaticRequester{ - Type: types.TypeUser, - OrgRole: identity.RoleAdmin, - }, - obj: &iamv0alpha1.TeamBinding{ - Spec: iamv0alpha1.TeamBindingSpec{ - Subject: iamv0alpha1.TeamBindingspecSubject{ - Name: "test-user", - }, - TeamRef: iamv0alpha1.TeamBindingTeamRef{ - Name: "test-team", - }, - Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, - }, - }, - want: nil, - }, - { - name: "invalid team binding - invalid permission", - requester: &identity.StaticRequester{ - Type: types.TypeUser, - OrgRole: identity.RoleAdmin, - }, - obj: &iamv0alpha1.TeamBinding{ - Spec: iamv0alpha1.TeamBindingSpec{ - Subject: iamv0alpha1.TeamBindingspecSubject{ - Name: "test-user", - }, - TeamRef: iamv0alpha1.TeamBindingTeamRef{ - Name: "test-team", - }, - Permission: "invalid", - }, - }, - want: apierrors.NewBadRequest("invalid permission"), - }, - { - name: "invalid team binding - no subject", - requester: &identity.StaticRequester{ - Type: types.TypeUser, - OrgRole: identity.RoleAdmin, - }, - obj: &iamv0alpha1.TeamBinding{ - Spec: iamv0alpha1.TeamBindingSpec{ - Subject: iamv0alpha1.TeamBindingspecSubject{ - Name: "", - }, - TeamRef: iamv0alpha1.TeamBindingTeamRef{ - Name: "test-team", - }, - Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, - }, - }, - want: apierrors.NewBadRequest("subject is required"), - }, - { - name: "invalid team binding - no teamRef", - requester: &identity.StaticRequester{ - Type: types.TypeUser, - OrgRole: identity.RoleAdmin, - }, - obj: &iamv0alpha1.TeamBinding{ - Spec: iamv0alpha1.TeamBindingSpec{ - Subject: iamv0alpha1.TeamBindingspecSubject{ - Name: "test-user", - }, - TeamRef: iamv0alpha1.TeamBindingTeamRef{ - Name: "", - }, - Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, - }, - }, - want: apierrors.NewBadRequest("teamRef is required"), - }, - { - name: "invalid team binding - no requester in context", - requester: nil, - obj: &iamv0alpha1.TeamBinding{ - Spec: iamv0alpha1.TeamBindingSpec{ - Subject: iamv0alpha1.TeamBindingspecSubject{ - Name: "test-user", - }, - TeamRef: iamv0alpha1.TeamBindingTeamRef{ - Name: "test-team", - }, - Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, - }, - }, - want: apierrors.NewUnauthorized("no identity found"), - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - ctx := identity.WithRequester(context.Background(), test.requester) - err := ValidateOnBindingCreate(ctx, test.obj) - assert.Equal(t, test.want, err) - }) - } -} diff --git a/pkg/registry/apis/iam/team/store_binding.go b/pkg/registry/apis/iam/teambinding/store.go similarity index 92% rename from pkg/registry/apis/iam/team/store_binding.go rename to pkg/registry/apis/iam/teambinding/store.go index 35f28692ca9..7f8f673334e 100644 --- a/pkg/registry/apis/iam/team/store_binding.go +++ b/pkg/registry/apis/iam/teambinding/store.go @@ -1,4 +1,4 @@ -package team +package teambinding import ( "context" @@ -15,7 +15,6 @@ import ( claims "github.com/grafana/authlib/types" iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" - iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/iam/common" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" @@ -74,20 +73,20 @@ func (l *LegacyBindingStore) ConvertToTable(ctx context.Context, object runtime. } func (l *LegacyBindingStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "update") + return nil, false, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "update") } func (l *LegacyBindingStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "delete") + return nil, false, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "delete") } func (l *LegacyBindingStore) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) { - return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "deleteCollection") + return nil, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "deleteCollection") } func (l *LegacyBindingStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { if !l.enableAuthnMutation { - return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "create") + return nil, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "create") } ns, err := request.NamespaceInfoFrom(ctx, true) @@ -168,7 +167,7 @@ func (l *LegacyBindingStore) Get(ctx context.Context, name string, options *meta if len(res.Bindings) != 1 { // FIXME: maybe empty result? - return nil, resource.NewNotFound(name) + return nil, bindingResource.NewNotFound(name) } obj := mapToBindingObject(ns, res.Bindings[0]) @@ -260,11 +259,3 @@ func mapFromBindingName(name string) (int64, int64) { return teamID, userID } - -func mapPermisson(p team.PermissionType) iamv0.TeamPermission { - if p == team.PermissionTypeAdmin { - return iamv0.TeamPermissionAdmin - } else { - return iamv0.TeamPermissionMember - } -} diff --git a/pkg/registry/apis/iam/teambinding/validate.go b/pkg/registry/apis/iam/teambinding/validate.go new file mode 100644 index 00000000000..27289ac19d2 --- /dev/null +++ b/pkg/registry/apis/iam/teambinding/validate.go @@ -0,0 +1,31 @@ +package teambinding + +import ( + "context" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + + iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +func ValidateOnCreate(ctx context.Context, obj *iamv0alpha1.TeamBinding) error { + _, err := identity.GetRequester(ctx) + if err != nil { + return apierrors.NewUnauthorized("no identity found") + } + + if obj.Spec.Permission != iamv0alpha1.TeamBindingTeamPermissionAdmin && obj.Spec.Permission != iamv0alpha1.TeamBindingTeamPermissionMember { + return apierrors.NewBadRequest("invalid permission") + } + + if obj.Spec.Subject.Name == "" { + return apierrors.NewBadRequest("subject is required") + } + + if obj.Spec.TeamRef.Name == "" { + return apierrors.NewBadRequest("teamRef is required") + } + + return nil +} diff --git a/pkg/registry/apis/iam/teambinding/validate_test.go b/pkg/registry/apis/iam/teambinding/validate_test.go new file mode 100644 index 00000000000..fbe4063fb43 --- /dev/null +++ b/pkg/registry/apis/iam/teambinding/validate_test.go @@ -0,0 +1,123 @@ +package teambinding + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + "github.com/grafana/authlib/types" + iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +func TestValidateOnCreate(t *testing.T) { + tests := []struct { + name string + requester *identity.StaticRequester + obj *iamv0alpha1.TeamBinding + want error + }{ + { + name: "valid team binding create", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: nil, + }, + { + name: "invalid team binding - invalid permission", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: "invalid", + }, + }, + want: apierrors.NewBadRequest("invalid permission"), + }, + { + name: "invalid team binding - no subject", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: apierrors.NewBadRequest("subject is required"), + }, + { + name: "invalid team binding - no teamRef", + requester: &identity.StaticRequester{ + Type: types.TypeUser, + OrgRole: identity.RoleAdmin, + }, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: apierrors.NewBadRequest("teamRef is required"), + }, + { + name: "invalid team binding - no requester in context", + requester: nil, + obj: &iamv0alpha1.TeamBinding{ + Spec: iamv0alpha1.TeamBindingSpec{ + Subject: iamv0alpha1.TeamBindingspecSubject{ + Name: "test-user", + }, + TeamRef: iamv0alpha1.TeamBindingTeamRef{ + Name: "test-team", + }, + Permission: iamv0alpha1.TeamBindingTeamPermissionAdmin, + }, + }, + want: apierrors.NewUnauthorized("no identity found"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := identity.WithRequester(context.Background(), test.requester) + err := ValidateOnCreate(ctx, test.obj) + assert.Equal(t, test.want, err) + }) + } +} From 52007d4854c8152c66a0c1112989f421500c5401 Mon Sep 17 00:00:00 2001 From: Mihai Doarna <mihai.doarna@grafana.com> Date: Thu, 16 Oct 2025 12:01:22 +0300 Subject: [PATCH 71/75] IAM: Add uid column in team_member DB table (#112439) * add uid column in team_member DB table * generate openapi --- pkg/services/sqlstore/migrations/team_mig.go | 13 +++++++++++++ pkg/services/team/model.go | 14 ++++++++------ pkg/services/team/teamimpl/store.go | 4 +++- public/api-merged.json | 3 +++ public/openapi3.json | 3 +++ 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/pkg/services/sqlstore/migrations/team_mig.go b/pkg/services/sqlstore/migrations/team_mig.go index 41fda158afe..bb4041a3766 100644 --- a/pkg/services/sqlstore/migrations/team_mig.go +++ b/pkg/services/sqlstore/migrations/team_mig.go @@ -83,4 +83,17 @@ func addTeamMigrations(mg *Migrator) { Name: "permission", Type: DB_SmallInt, Nullable: true, })) mg.AddMigration("add unique index team_member_user_id_org_id", NewAddIndexMigration(teamMemberV1, teamMemberV1.Indices[3])) + + mg.AddMigration("Add column uid in team_member", NewAddColumnMigration(teamMemberV1, &Column{ + Name: "uid", Type: DB_NVarchar, Length: 40, Nullable: true, + })) + + mg.AddMigration("Update uid column values in team_member", NewRawSQLMigration(""). + SQLite("UPDATE team_member SET uid=printf('tm%09d',id) WHERE uid IS NULL OR uid = '';"). + Postgres("UPDATE team_member SET uid='tm' || lpad('' || id::text,9,'0') WHERE uid IS NULL OR uid = '';"). + Mysql("UPDATE team_member SET uid=concat('tm',lpad(id,9,'0')) WHERE uid IS NULL OR uid = '';")) + + mg.AddMigration("Add unique index team_member_uid", NewAddIndexMigration(teamMemberV1, &Index{ + Cols: []string{"uid"}, Type: UniqueIndex, + })) } diff --git a/pkg/services/team/model.go b/pkg/services/team/model.go index eead8de614f..68f655771b0 100644 --- a/pkg/services/team/model.go +++ b/pkg/services/team/model.go @@ -130,11 +130,12 @@ type SearchTeamQueryResult struct { // TeamMember model type TeamMember struct { - ID int64 `xorm:"pk autoincr 'id'"` - OrgID int64 `xorm:"org_id"` - TeamID int64 `xorm:"team_id"` - UserID int64 `xorm:"user_id"` - External bool // Signals that the membership has been created by an external systems, such as LDAP + ID int64 `xorm:"pk autoincr 'id'"` + UID string `xorm:"uid"` + OrgID int64 `xorm:"org_id"` + TeamID int64 `xorm:"team_id"` + UserID int64 `xorm:"user_id"` + External bool // Signals that the membership has been created by an external systems, such as LDAP Permission PermissionType Created time.Time @@ -180,9 +181,10 @@ type GetTeamMembersQuery struct { // Projections and DTOs type TeamMemberDTO struct { + UID string `json:"uid" xorm:"uid"` OrgID int64 `json:"orgId" xorm:"org_id"` TeamID int64 `json:"teamId" xorm:"team_id"` - TeamUID string `json:"teamUID" xorm:"uid"` + TeamUID string `json:"teamUID" xorm:"team_uid"` UserID int64 `json:"userId" xorm:"user_id"` UserUID string `json:"userUID" xorm:"user_uid"` External bool `json:"-"` diff --git a/pkg/services/team/teamimpl/store.go b/pkg/services/team/teamimpl/store.go index d7502c48fc2..6008bd9c1d0 100644 --- a/pkg/services/team/teamimpl/store.go +++ b/pkg/services/team/teamimpl/store.go @@ -423,6 +423,7 @@ func addTeamMember(sess *db.Session, orgID, teamID, userID int64, isExternal boo } entity := team.TeamMember{ + UID: util.GenerateShortUID(), OrgID: orgID, TeamID: teamID, UserID: userID, @@ -559,6 +560,7 @@ func (ss *xormStore) getTeamMembers(ctx context.Context, query *team.GetTeamMemb sess.Select(fmt.Sprintf(`team_member.org_id, team_member.team_id, team_member.user_id, + team_member.uid, %[1]s.email, %[1]s.name, %[1]s.login, @@ -566,7 +568,7 @@ func (ss *xormStore) getTeamMembers(ctx context.Context, query *team.GetTeamMemb team_member.external, team_member.permission, user_auth.auth_module, - team.uid`, ss.db.GetDialect().Quote("user"))) + team.uid as team_uid`, ss.db.GetDialect().Quote("user"))) sess.Asc("user.login", "user.email") err := sess.Find(&queryResult) diff --git a/public/api-merged.json b/public/api-merged.json index 83211911e0d..7fb2a644043 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -21999,6 +21999,9 @@ "teamUID": { "type": "string" }, + "uid": { + "type": "string" + }, "userId": { "type": "integer", "format": "int64" diff --git a/public/openapi3.json b/public/openapi3.json index 162af40b01c..ff6255565ae 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -11507,6 +11507,9 @@ "teamUID": { "type": "string" }, + "uid": { + "type": "string" + }, "userId": { "format": "int64", "type": "integer" From 6e66a98ef761509cd0eb687ea17732ea8398c3c0 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE <gamab@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:54:38 +0200 Subject: [PATCH 72/75] `grafana-iam`: skip flaky TestAfterResourcePermissionCreate test (#112504) `grafana-iam`: TestAfterResourcePermissionCreate flaky test skip --- pkg/registry/apis/iam/hooks_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/registry/apis/iam/hooks_test.go b/pkg/registry/apis/iam/hooks_test.go index 3ee0c7bcd24..d292aa823a2 100644 --- a/pkg/registry/apis/iam/hooks_test.go +++ b/pkg/registry/apis/iam/hooks_test.go @@ -24,6 +24,8 @@ func (f *FakeZanzanaClient) Write(ctx context.Context, req *v1.WriteRequest) err } func TestAfterResourcePermissionCreate(t *testing.T) { + t.Skip("Need to fix its flaky behavior in CI") + b := &IdentityAccessManagementAPIBuilder{ logger: log.NewNopLogger(), zTickets: make(chan bool, 1), From ebd0d334bc99eb6bd978fb6f712cae023e100325 Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba <ivanortegaalba@gmail.com> Date: Thu, 16 Oct 2025 12:45:29 +0200 Subject: [PATCH 73/75] Dashboards: Fix preload field not being persisted via /v1beta1 (#112475) --- apps/dashboard/pkg/migration/frontend_defaults.go | 1 - .../dev-dashboards-output/datasource-testdata/demo1.v42.json | 1 + .../panel-canvas/canvas-connection-examples.v42.json | 1 + .../dev-dashboards-output/panel-table/table_footer.v42.json | 1 + .../panel-table/table_kitchen_sink.v42.json | 1 + .../dev-dashboards-output/panel-table/table_markdown.v42.json | 1 + .../panel-table/table_sparkline_cell.v42.json | 1 + .../dev-dashboards-output/panel-table/table_tests_new.v42.json | 1 + .../panel-table/table_v12_2_migrations.v42.json | 1 + .../panel-timeline/timeline-thresholds-mappings.v42.json | 1 + .../panel-timeseries/timeseries-nulls.v42.json | 1 + .../dev-dashboards-output/panel-trend/trend_example.v42.json | 1 + .../dev-dashboards-output/panel-xychart/xychart-demo.v42.json | 1 + .../scenarios/mostly-blank-dashboard.v42.json | 1 + .../transforms/regression-analysis.v42.json | 1 + public/app/features/dashboard/state/DashboardModel.ts | 2 ++ 16 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/dashboard/pkg/migration/frontend_defaults.go b/apps/dashboard/pkg/migration/frontend_defaults.go index 0451e45b8de..c376be6f47f 100644 --- a/apps/dashboard/pkg/migration/frontend_defaults.go +++ b/apps/dashboard/pkg/migration/frontend_defaults.go @@ -1061,7 +1061,6 @@ func cleanupDashboardDefaults(dashboard map[string]interface{}) { // Remove transient properties that frontend filters out during getSaveModelClone() // These properties are lost during frontend's property copying loop in getSaveModelCloneOld() - delete(dashboard, "preload") // Transient dashboard loading state delete(dashboard, "iteration") // Template variable iteration timestamp delete(dashboard, "nav") // Removed after V7 migration delete(dashboard, "pulldowns") // Removed after V6 migration - frontend doesn't have this property diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/datasource-testdata/demo1.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/datasource-testdata/demo1.v42.json index c8986249791..323bac1d906 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/datasource-testdata/demo1.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/datasource-testdata/demo1.v42.json @@ -1293,6 +1293,7 @@ "type": "timeseries" } ], + "preload": false, "refresh": false, "schemaVersion": 42, "tags": [ diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-canvas/canvas-connection-examples.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-canvas/canvas-connection-examples.v42.json index d9cf29cad55..99cb1d0b28e 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-canvas/canvas-connection-examples.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-canvas/canvas-connection-examples.v42.json @@ -3941,6 +3941,7 @@ "type": "canvas" } ], + "preload": false, "refresh": "", "schemaVersion": 42, "tags": [ diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_footer.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_footer.v42.json index 13c13c8bccd..21bbf7b4d39 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_footer.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_footer.v42.json @@ -1537,6 +1537,7 @@ "type": "table" } ], + "preload": false, "schemaVersion": 42, "tags": [], "templating": { diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_kitchen_sink.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_kitchen_sink.v42.json index 501fcdef0d6..515ab48b7bf 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_kitchen_sink.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_kitchen_sink.v42.json @@ -1848,6 +1848,7 @@ "type": "table" } ], + "preload": false, "schemaVersion": 42, "tags": [], "templating": { diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_markdown.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_markdown.v42.json index 3cd280f3c55..9ed4d3f873b 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_markdown.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_markdown.v42.json @@ -107,6 +107,7 @@ "type": "table" } ], + "preload": false, "schemaVersion": 42, "tags": [], "templating": { diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json index 11b54f3f3a5..3c8310630d7 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json @@ -576,6 +576,7 @@ "type": "table" } ], + "preload": false, "refresh": "", "schemaVersion": 42, "tags": [], diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_tests_new.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_tests_new.v42.json index 9a7ac8fcfc9..b00ef6aa5ab 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_tests_new.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_tests_new.v42.json @@ -982,6 +982,7 @@ "type": "table" } ], + "preload": false, "refresh": "", "schemaVersion": 42, "tags": [ diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_v12_2_migrations.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_v12_2_migrations.v42.json index 7160b7d4af2..5054b2e1325 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_v12_2_migrations.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_v12_2_migrations.v42.json @@ -2172,6 +2172,7 @@ "type": "table" } ], + "preload": false, "schemaVersion": 42, "tags": [], "templating": { diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeline/timeline-thresholds-mappings.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeline/timeline-thresholds-mappings.v42.json index f1d9ec747cd..c5102363f3f 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeline/timeline-thresholds-mappings.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeline/timeline-thresholds-mappings.v42.json @@ -1071,6 +1071,7 @@ "type": "state-timeline" } ], + "preload": false, "refresh": "", "schemaVersion": 42, "tags": [ diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeseries/timeseries-nulls.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeseries/timeseries-nulls.v42.json index a75a3d47ee4..1e8a8b30326 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeseries/timeseries-nulls.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-timeseries/timeseries-nulls.v42.json @@ -3409,6 +3409,7 @@ "type": "state-timeline" } ], + "preload": false, "refresh": false, "schemaVersion": 42, "tags": [ diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-trend/trend_example.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-trend/trend_example.v42.json index 21c283d5596..212565adbca 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-trend/trend_example.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-trend/trend_example.v42.json @@ -196,6 +196,7 @@ "type": "trend" } ], + "preload": false, "refresh": "", "schemaVersion": 42, "tags": [ diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-xychart/xychart-demo.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-xychart/xychart-demo.v42.json index 2c9d0b026b4..3a6f7e88133 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-xychart/xychart-demo.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-xychart/xychart-demo.v42.json @@ -1433,6 +1433,7 @@ "type": "xychart" } ], + "preload": false, "refresh": "", "schemaVersion": 42, "tags": [ diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/scenarios/mostly-blank-dashboard.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/scenarios/mostly-blank-dashboard.v42.json index b264fc91cb9..7902e41bb2e 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/scenarios/mostly-blank-dashboard.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/scenarios/mostly-blank-dashboard.v42.json @@ -79,6 +79,7 @@ "type": "debug" } ], + "preload": false, "refresh": "", "schemaVersion": 42, "tags": [], diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/transforms/regression-analysis.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/transforms/regression-analysis.v42.json index caa608c1732..00309402356 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/transforms/regression-analysis.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/transforms/regression-analysis.v42.json @@ -1049,6 +1049,7 @@ "type": "xychart" } ], + "preload": false, "refresh": "", "schemaVersion": 42, "tags": [ diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 65dd2b7308b..f4b912c6358 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -81,6 +81,7 @@ export class DashboardModel implements TimeModel { graphTooltip: DashboardCursorSync; time: any; liveNow?: boolean; + preload?: boolean; private originalTime: any; timepicker: any; templating: { list: any[] }; @@ -154,6 +155,7 @@ export class DashboardModel implements TimeModel { this.timezone = data.timezone ?? ''; this.weekStart = data.weekStart ?? ''; this.editable = data.editable !== false; + this.preload = data.preload; this.graphTooltip = data.graphTooltip || 0; this.time = data.time ?? { from: 'now-6h', to: 'now' }; this.timepicker = data.timepicker ?? {}; From f03fb6fe8a46e0caad147d1218fe9580159e7008 Mon Sep 17 00:00:00 2001 From: Alex Khomenko <Clarity-89@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:06:51 +0300 Subject: [PATCH 74/75] Restore dashboards: Add documentation (#112272) * Restore dashboards: Add docs * Update docs/sources/dashboards/manage-dashboards/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/dashboards/manage-dashboards/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/dashboards/manage-dashboards/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Review feedback * Clarify name --------- Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> --- .../dashboards/manage-dashboards/index.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/sources/visualizations/dashboards/manage-dashboards/index.md b/docs/sources/visualizations/dashboards/manage-dashboards/index.md index 83052314134..268b7a7ad8e 100644 --- a/docs/sources/visualizations/dashboards/manage-dashboards/index.md +++ b/docs/sources/visualizations/dashboards/manage-dashboards/index.md @@ -115,6 +115,39 @@ Changes are saved automatically. For more information about dashboard permissions, refer to [Dashboard permissions](ref:dashboard-permissions). +## Dashboard and folder deletion + +**Folders** - When you delete folders, the action is immediate and can't be reversed. + +**Dashboards** - Deleted dashboards are stored in the deletion history and can be [restored](#restore-dashboards) if needed (Cloud only). + +## Restore deleted dashboards + +{{% admonition type="caution" %}} +The feature is only available in Grafana Cloud. +{{% /admonition %}} + +When you delete dashboards, they’re stored in the deletion history for up to 12 months, allowing you to restore them if needed. +The deletion history can contain a maximum of 1000 dashboards. +After this limit is reached, the oldest deleted dashboards are permanently removed even if the 12-month expiration period isn't over yet. + +You can access the list of deleted dashboards from the **Dashboards** page by clicking the **Recently deleted** button, or by navigating to **Dashboards > Recently deleted**. + +To restore one or more dashboards, follow these steps: + +1. In the main menu, click **Dashboards > Recently deleted** or click the **Recently deleted** button from the **Dashboards** page. +2. Select the dashboards you want to restore. +3. Click **Restore** and select a folder to which the dashboards will be moved: + - If you select one dashboard and its original folder still exists, that folder is preselected. + - If you select multiple dashboards from the _same_ folder, that folder is preselected. + - If you select dashboards from _different_ folders, you’ll need to choose one target folder for all of them. + - If the original folder no longer exists, you’ll need to select a new target folder. +4. Click **Restore**. + +{{% admonition type="note" %}} +Only users with admin rights can access the **Restore dashboards** page. +{{% /admonition %}} + ## Set up generative AI features for dashboards You can use generative AI to help you with the following tasks: From ffee922fae8f58f868eacd28ed9b6c9f820b9b92 Mon Sep 17 00:00:00 2001 From: Tania <10127682+undef1nd@users.noreply.github.com> Date: Thu, 16 Oct 2025 13:37:51 +0200 Subject: [PATCH 75/75] OpenFeature: Include HTTP status code and evaluation context in traces (#112422) * Add HTTP status codes to handlers spans * Add a span for namespace validation --- pkg/registry/apis/ofrep/register.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pkg/registry/apis/ofrep/register.go b/pkg/registry/apis/ofrep/register.go index 49c755ecc92..cac6fc444d8 100644 --- a/pkg/registry/apis/ofrep/register.go +++ b/pkg/registry/apis/ofrep/register.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -252,6 +253,7 @@ func (b *APIBuilder) oneFlagHandler(w http.ResponseWriter, r *http.Request) { if !b.validateNamespace(r) { _ = tracing.Errorf(span, namespaceMismatchMsg) + span.SetAttributes(semconv.HTTPStatusCode(http.StatusUnauthorized)) b.logger.Error(namespaceMismatchMsg) http.Error(w, namespaceMismatchMsg, http.StatusUnauthorized) return @@ -260,6 +262,7 @@ func (b *APIBuilder) oneFlagHandler(w http.ResponseWriter, r *http.Request) { flagKey := mux.Vars(r)["flagKey"] if flagKey == "" { _ = tracing.Errorf(span, "flagKey parameter is required") + span.SetAttributes(semconv.HTTPStatusCode(http.StatusBadRequest)) http.Error(w, "flagKey parameter is required", http.StatusBadRequest) return } @@ -272,6 +275,7 @@ func (b *APIBuilder) oneFlagHandler(w http.ResponseWriter, r *http.Request) { // Unless the request is authenticated, we only allow public flags evaluations if !isAuthedReq && !isPublicFlag(flagKey) { _ = tracing.Errorf(span, "unauthorized to evaluate flag: %s", flagKey) + span.SetAttributes(semconv.HTTPStatusCode(http.StatusUnauthorized)) b.logger.Error("Unauthorized to evaluate flag", "flagKey", flagKey) http.Error(w, "unauthorized to evaluate flag", http.StatusUnauthorized) return @@ -293,6 +297,7 @@ func (b *APIBuilder) allFlagsHandler(w http.ResponseWriter, r *http.Request) { if !b.validateNamespace(r) { _ = tracing.Errorf(span, namespaceMismatchMsg) + span.SetAttributes(semconv.HTTPStatusCode(http.StatusUnauthorized)) b.logger.Error(namespaceMismatchMsg) http.Error(w, namespaceMismatchMsg, http.StatusUnauthorized) return @@ -357,7 +362,10 @@ func (b *APIBuilder) isAuthenticatedRequest(r *http.Request) bool { // validateNamespace checks if the namespace in the evaluation context matches the namespace in the request func (b *APIBuilder) validateNamespace(r *http.Request) bool { - // Extract namespace from request context or URL path + ctx, span := tracer.Start(r.Context(), "ofrep.validateNamespace") + defer span.End() + r = r.WithContext(ctx) + var namespace string user, ok := types.AuthInfoFrom(r.Context()) if !ok { @@ -370,19 +378,25 @@ func (b *APIBuilder) validateNamespace(r *http.Request) bool { namespace = mux.Vars(r)["namespace"] } - // Extract namespace from feature flag evaluation context + // Read request body for namespace validation and tracing body, err := io.ReadAll(r.Body) if err != nil { + _ = tracing.Errorf(span, "failed to read request body: %w", err) b.logger.Error("Error reading evaluation request body", "error", err) + span.SetAttributes(attribute.Bool("validation.success", false)) return false } r.Body = io.NopCloser(bytes.NewBuffer(body)) + span.SetAttributes(attribute.String("request.body", string(body))) + evalCtxNamespace := b.namespaceFromEvalCtx(body) // "default" namespace case can only occur in on-prem grafana if (namespace == "default" && evalCtxNamespace == "") || (evalCtxNamespace == namespace) { + span.SetAttributes(attribute.Bool("validation.success", true)) return true } + span.SetAttributes(attribute.Bool("validation.success", false)) return false }