From 86cb5d8af739745a1173496ba76a545efd45dfd8 Mon Sep 17 00:00:00 2001 From: Chris Chang <51393127+chriscerie@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:58:58 +0900 Subject: [PATCH 01/72] Cloudwatch: Add missing AWS regions (#113010) Add missing cw regions --- pkg/tsdb/cloudwatch/constants/metrics.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/tsdb/cloudwatch/constants/metrics.go b/pkg/tsdb/cloudwatch/constants/metrics.go index f52207001ec..02da8f26a7a 100644 --- a/pkg/tsdb/cloudwatch/constants/metrics.go +++ b/pkg/tsdb/cloudwatch/constants/metrics.go @@ -17,6 +17,7 @@ func Regions() RegionsSet { "ap-southeast-3": {}, "ap-southeast-4": {}, "ap-southeast-5": {}, + "ap-southeast-6": {}, "ap-southeast-7": {}, "ca-central-1": {}, "ca-west-1": {}, @@ -24,6 +25,7 @@ func Regions() RegionsSet { "cn-northwest-1": {}, "eu-central-1": {}, "eu-central-2": {}, + "eu-isoe-west-1": {}, "eu-north-1": {}, "eu-south-1": {}, "eu-south-2": {}, @@ -40,7 +42,11 @@ func Regions() RegionsSet { "us-gov-east-1": {}, "us-gov-west-1": {}, "us-iso-east-1": {}, + "us-iso-west-1": {}, "us-isob-east-1": {}, + "us-isob-west-1": {}, + "us-isof-east-1": {}, + "us-northeast-1": {}, "us-west-1": {}, "us-west-2": {}, } From c0b8fc6e6c35b59e9fcb15cd94d70940ae1907e9 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 29 Oct 2025 14:06:35 +0300 Subject: [PATCH 02/72] Chore: update storybook/test-runner (#113154) --- package.json | 2 +- yarn.lock | 38 +++++++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 37df4fb1c32..503049f3ed2 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "@grafana/test-utils": "workspace:*", "@manypkg/get-packages": "^3.0.0", "@npmcli/package-json": "^6.0.0", - "@playwright/test": "1.54.1", + "@playwright/test": "1.55.1", "@pmmmwh/react-refresh-webpack-plugin": "0.6.1", "@react-types/button": "3.13.0", "@react-types/menu": "3.10.3", diff --git a/yarn.lock b/yarn.lock index b281216a8e8..9c9e587d570 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6469,14 +6469,14 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:1.54.1": - version: 1.54.1 - resolution: "@playwright/test@npm:1.54.1" +"@playwright/test@npm:1.55.1": + version: 1.55.1 + resolution: "@playwright/test@npm:1.55.1" dependencies: - playwright: "npm:1.54.1" + playwright: "npm:1.55.1" bin: playwright: cli.js - checksum: 10/75ea34818f57f11bd8a1e6e3ac202c752c520c98467607be67c0eccfe41ea8ff6ae6824e91cb5ecacbee403aef4e1e6210500ee3cc82dd9317a8ee7136af18d3 + checksum: 10/c67a46353c58aaeac551bce2654cdef0e9a0ad76b1667514832d34acd4b26ec72f35ea7595cd3fad4c4e1e039d5bb876b8d62c89af4525d455285f6fff9f0642 languageName: node linkType: hard @@ -18835,7 +18835,7 @@ __metadata: "@opentelemetry/api": "npm:1.9.0" "@opentelemetry/exporter-collector": "npm:0.25.0" "@opentelemetry/semantic-conventions": "npm:1.37.0" - "@playwright/test": "npm:1.54.1" + "@playwright/test": "npm:1.55.1" "@pmmmwh/react-refresh-webpack-plugin": "npm:0.6.1" "@popperjs/core": "npm:2.11.8" "@react-aria/dialog": "npm:3.5.31" @@ -26313,7 +26313,31 @@ __metadata: languageName: node linkType: hard -"playwright@npm:1.54.1, playwright@npm:^1.14.0": +"playwright-core@npm:1.55.1": + version: 1.55.1 + resolution: "playwright-core@npm:1.55.1" + bin: + playwright-core: cli.js + checksum: 10/953a43039dbcca04513bd3138a9dee249a136d5377da00d49402ffcd24d33ca84dc1dc04636d1b76e9f8c9fd28a302b89cda1ae544d72b5d829c28e623bfcb0b + languageName: node + linkType: hard + +"playwright@npm:1.55.1": + version: 1.55.1 + resolution: "playwright@npm:1.55.1" + dependencies: + fsevents: "npm:2.3.2" + playwright-core: "npm:1.55.1" + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 10/5dcf9ce564cacf6c06ebc864bb2b1f709c641792560d49889ed4c98e230be54a963ec8aaafff11269735d8d22da4900bd2d4ef9f1748d132326ffda8fb1f3f20 + languageName: node + linkType: hard + +"playwright@npm:^1.14.0": version: 1.54.1 resolution: "playwright@npm:1.54.1" dependencies: From 6093afddd4991a390030000f8c43eaf222849cf4 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Wed, 29 Oct 2025 11:21:58 +0000 Subject: [PATCH 03/72] Folders: Migrate FolderFilter component to ComboBox (#113047) --- .github/CODEOWNERS | 2 +- .../src/handlers/api/search/handlers.ts | 7 +- .../FolderFilter/FolderFilter.test.tsx | 46 +++++++ .../components/FolderFilter/FolderFilter.tsx | 44 ++---- .../LibraryPanelsSearch.test.tsx | 129 +++++++----------- .../LibraryPanelsSearch.tsx | 3 +- public/locales/en-US/grafana.json | 1 - 7 files changed, 122 insertions(+), 110 deletions(-) create mode 100644 public/app/core/components/FolderFilter/FolderFilter.test.tsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 853f27774d4..ea0e83e3baa 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -798,7 +798,7 @@ playwright.storybook.config.ts @grafana/grafana-frontend-platform /public/app/core/components/ColorScale/ @grafana/dataviz-squad /public/app/core/components/DynamicImports/ @grafana/grafana-search-navigate-organise /public/app/core/components/EmptyListCTA/ @grafana/grafana-frontend-platform -/public/app/core/components/FolderFilter/ @grafana/sharing-squad +/public/app/core/components/FolderFilter/ @grafana/grafana-search-navigate-organise /public/app/core/components/Footer/ @grafana/grafana-search-navigate-organise /public/app/core/components/ForgottenPassword/ @grafana/grafana-search-navigate-organise /public/app/core/components/Form/ @grafana/grafana-frontend-platform 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 2aebd03b174..d9fa090fbf1 100644 --- a/packages/grafana-test-utils/src/handlers/api/search/handlers.ts +++ b/packages/grafana-test-utils/src/handlers/api/search/handlers.ts @@ -17,12 +17,17 @@ const slugify = (str: string) => { .replace(/ +/g, '-'); }; +const typeFilterMap: Record = { + 'dash-db': 'dashboard', + 'dash-folder': 'folder', +}; + const getLegacySearchHandler = () => http.get('/api/search', ({ request }) => { const folderFilter = new URL(request.url).searchParams.get('folderUIDs') || null; const typeFilter = new URL(request.url).searchParams.get('type') || null; // Workaround for the fixture kind being 'dashboard' instead of 'dash-db' - const mappedTypeFilter = typeFilter === 'dash-db' ? 'dashboard' : typeFilter; + const mappedTypeFilter = typeFilter ? typeFilterMap[typeFilter] || typeFilter : null; const starredFilter = new URL(request.url).searchParams.get('starred') || null; const tagFilter = new URL(request.url).searchParams.getAll('tag') || null; diff --git a/public/app/core/components/FolderFilter/FolderFilter.test.tsx b/public/app/core/components/FolderFilter/FolderFilter.test.tsx new file mode 100644 index 00000000000..06f0cf62a2a --- /dev/null +++ b/public/app/core/components/FolderFilter/FolderFilter.test.tsx @@ -0,0 +1,46 @@ +import { comboboxTestSetup } from 'test/helpers/comboboxTestSetup'; +import { render, screen, testWithFeatureToggles } from 'test/test-utils'; + +import { setBackendSrv } from '@grafana/runtime'; +import { setupMockServer } from '@grafana/test-utils/server'; +import { getFolderFixtures } from '@grafana/test-utils/unstable'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { resetGrafanaSearcher } from 'app/features/search/service/searcher'; + +import { FolderFilter } from './FolderFilter'; +const [_, { folderA, folderB }] = getFolderFixtures(); + +setBackendSrv(backendSrv); +setupMockServer(); +comboboxTestSetup(); + +const fixtures: Array< + [ + // Test title + string, + // Feature toggle setup + Parameters[0], + ] +> = [ + ['app platform APIs enabled', { enable: ['unifiedStorageSearchUI'] }], + ['app platform APIs disabled', {}], +]; + +describe.each(fixtures)('FolderFilter - %s', (_title, featureToggleSetup) => { + beforeEach(() => { + resetGrafanaSearcher(); + }); + + testWithFeatureToggles(featureToggleSetup); + + it('allows selecting folders', async () => { + const onChange = jest.fn(); + const { user } = render(); + + await user.click(screen.getByPlaceholderText('Filter by folder')); + + await user.click(await screen.findByText(folderA.item.title)); + await user.click(await screen.findByText(folderB.item.title)); + expect(onChange).toHaveBeenCalledWith([folderA.item.uid, folderB.item.uid]); + }); +}); diff --git a/public/app/core/components/FolderFilter/FolderFilter.tsx b/public/app/core/components/FolderFilter/FolderFilter.tsx index c98490ea0c7..1f8f3e5557d 100644 --- a/public/app/core/components/FolderFilter/FolderFilter.tsx +++ b/public/app/core/components/FolderFilter/FolderFilter.tsx @@ -1,25 +1,17 @@ -import debounce from 'debounce-promise'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useState } from 'react'; -import { SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { AsyncMultiSelect, Icon } from '@grafana/ui'; +import { ComboboxOption, MultiCombobox } from '@grafana/ui'; import { getGrafanaSearcher } from 'app/features/search/service/searcher'; -import { FolderInfo } from 'app/types/folders'; export interface FolderFilterProps { - onChange: (folder: FolderInfo[]) => void; - maxMenuHeight?: number; + onChange: (folder: string[]) => void; } -export function FolderFilter({ onChange, maxMenuHeight }: FolderFilterProps): JSX.Element { - const [loading, setLoading] = useState(false); - const getOptions = useCallback((searchString: string) => getFoldersAsOptions(searchString, setLoading), []); - const debouncedLoadOptions = useMemo(() => debounce(getOptions, 300), [getOptions]); - - const [value, setValue] = useState>>([]); +export function FolderFilter({ onChange }: FolderFilterProps): JSX.Element { + const [value, setValue] = useState([]); const onSelectOptionChange = useCallback( - (folders: Array>) => { + (folders: ComboboxOption[]) => { const changedFolderIds = folders.filter((f) => Boolean(f.value)).map((f) => f.value!); onChange(changedFolderIds); setValue(folders); @@ -28,26 +20,21 @@ export function FolderFilter({ onChange, maxMenuHeight }: FolderFilterProps): JS ); return ( - } aria-label={t('folder-filter.select-aria-label', 'Folder filter')} - defaultOptions /> ); } -async function getFoldersAsOptions( - searchString: string, - setLoading: (loading: boolean) => void -): Promise>> { - setLoading(true); +async function getFoldersAsOptions(searchString: string) { // Use searcher as it will handle the logic for using the appropriate API const searcher = getGrafanaSearcher(); const queryResponse = await searcher.search({ @@ -59,13 +46,12 @@ async function getFoldersAsOptions( const options = queryResponse.view.map((item) => ({ label: item.name, - value: { uid: item.uid, title: item.name }, + value: item.uid, })); if (!searchString || 'dashboards'.includes(searchString.toLowerCase())) { - options.unshift({ label: 'Dashboards', value: { uid: 'general', title: 'Dashboards' } }); + options.unshift({ label: 'Dashboards', value: 'general' }); } - setLoading(false); return options; } diff --git a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx index bb938595b72..7cb1a99a864 100644 --- a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx @@ -1,10 +1,9 @@ -import { render, screen, waitFor, within } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { render, screen, waitFor, within } from 'test/test-utils'; import { PanelPluginMeta, PluginMetaInfo, PluginType } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { setBackendSrv } from '@grafana/runtime'; import { Panel } from '@grafana/schema'; -import { getGrafanaSearcher } from 'app/features/search/service/searcher'; +import { setupMockServer } from '@grafana/test-utils/server'; import { backendSrv } from '../../../../core/services/backend_srv'; import * as panelUtils from '../../../panel/state/util'; @@ -13,6 +12,29 @@ import { LibraryElementsSearchResult } from '../../types'; import { LibraryPanelsSearch, LibraryPanelsSearchProps } from './LibraryPanelsSearch'; +setBackendSrv(backendSrv); +setupMockServer(); + +const pluginInfo = { logos: { small: '', large: '' } } as PluginMetaInfo; +const graph: PanelPluginMeta = { + name: 'Graph', + id: 'graph', + info: pluginInfo, + baseUrl: '', + type: PluginType.panel, + module: '', + sort: 0, +}; +const timeseries: PanelPluginMeta = { + name: 'Time Series', + id: 'timeseries', + info: pluginInfo, + baseUrl: '', + type: PluginType.panel, + module: '', + sort: 1, +}; + jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), config: { @@ -26,79 +48,35 @@ jest.mock('@grafana/runtime', () => ({ }, })); -jest.mock('debounce-promise', () => { - const debounce = () => { - const debounced = () => - Promise.resolve([ - { label: 'Dashboards', value: { uid: '', title: 'Dashboards' } }, - { label: 'Folder1', value: { id: 'xMsQdBfWz', title: 'Folder1' } }, - { label: 'Folder2', value: { id: 'wfTJJL5Wz', title: 'Folder2' } }, - ]); - return debounced; - }; - - return debounce; -}); - +const getLibraryPanelsSpy = jest.spyOn(api, 'getLibraryPanels'); jest.spyOn(api, 'getConnectedDashboards').mockResolvedValue([]); jest.spyOn(api, 'deleteLibraryPanel').mockResolvedValue({ message: 'success' }); +jest.spyOn(panelUtils, 'getAllPanelPluginMeta').mockReturnValue([graph, timeseries]); + async function getTestContext( propOverrides: Partial = {}, searchResult: LibraryElementsSearchResult = { elements: [], perPage: 40, page: 1, totalCount: 0 } ) { - jest.clearAllMocks(); - const pluginInfo = { logos: { small: '', large: '' } } as PluginMetaInfo; - const graph: PanelPluginMeta = { - name: 'Graph', - id: 'graph', - info: pluginInfo, - baseUrl: '', - type: PluginType.panel, - module: '', - sort: 0, - }; - const timeseries: PanelPluginMeta = { - name: 'Time Series', - id: 'timeseries', - info: pluginInfo, - baseUrl: '', - type: PluginType.panel, - module: '', - sort: 1, - }; - - config.featureToggles = { panelTitleSearch: false }; - const getSpy = jest.spyOn(backendSrv, 'get'); - - jest.spyOn(getGrafanaSearcher(), 'getSortOptions').mockResolvedValue([ - { - label: 'Alphabetically (A–Z)', - value: 'alpha-asc', - }, - { - label: 'Alphabetically (Z–A)', - value: 'alpha-desc', - }, - ]); - - const getLibraryPanelsSpy = jest.spyOn(api, 'getLibraryPanels').mockResolvedValue(searchResult); - const getAllPanelPluginMetaSpy = jest.spyOn(panelUtils, 'getAllPanelPluginMeta').mockReturnValue([graph, timeseries]); + getLibraryPanelsSpy.mockResolvedValue(searchResult); const props: LibraryPanelsSearchProps = { onClick: jest.fn(), }; Object.assign(props, propOverrides); - const { rerender } = render(); + const view = render(); await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalled()); expect(getLibraryPanelsSpy).toHaveBeenCalledTimes(1); - jest.clearAllMocks(); - return { rerender, getLibraryPanelsSpy, getSpy, getAllPanelPluginMetaSpy }; + return view; } describe('LibraryPanelsSearch', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('when mounted with default options', () => { it('should show input filter and library panels view', async () => { await getTestContext(); @@ -109,9 +87,9 @@ describe('LibraryPanelsSearch', () => { describe('and user searches for library panel by name or description', () => { it('should call api with correct params', async () => { - const { getLibraryPanelsSpy } = await getTestContext(); + const { user } = await getTestContext(); - await userEvent.type(screen.getByPlaceholderText(/search by name/i), 'a'); + await user.type(screen.getByPlaceholderText(/search by name/i), 'a'); await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalled()); await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ @@ -138,9 +116,9 @@ describe('LibraryPanelsSearch', () => { describe('and user changes sorting', () => { it('should call api with correct params', async () => { - const { getLibraryPanelsSpy } = await getTestContext({ showSort: true }); + const { user } = await getTestContext({ showSort: true }); - await userEvent.type(screen.getByText(/sort \(default a–z\)/i), 'Desc{enter}'); + await user.type(screen.getByText(/sort \(default a–z\)/i), 'Desc{enter}'); await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ searchString: '', @@ -167,10 +145,10 @@ describe('LibraryPanelsSearch', () => { describe('and user changes panel filter', () => { it('should call api with correct params', async () => { - const { getLibraryPanelsSpy } = await getTestContext({ showPanelFilter: true }); + const { user } = await getTestContext({ showPanelFilter: true }); - await userEvent.type(screen.getByRole('combobox', { name: /panel type filter/i }), 'Graph{enter}'); - await userEvent.type(screen.getByRole('combobox', { name: /panel type filter/i }), 'Time Series{enter}'); + await user.type(screen.getByRole('combobox', { name: /panel type filter/i }), 'Graph{enter}'); + await user.type(screen.getByRole('combobox', { name: /panel type filter/i }), 'Time Series{enter}'); await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ searchString: '', @@ -191,12 +169,12 @@ describe('LibraryPanelsSearch', () => { expect(screen.getByPlaceholderText(/search by name/i)).toBeInTheDocument(); expect(screen.getByText(/you haven\'t created any library panels yet/i)).toBeInTheDocument(); - expect(screen.getByRole('combobox', { name: /folder filter/i })).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Filter by folder')).toBeInTheDocument(); }); describe('and user changes folder filter', () => { it('should call api with correct params', async () => { - const { getLibraryPanelsSpy } = await getTestContext( + const { user } = await getTestContext( { showFolderFilter: true, currentFolderUID: 'wXyZ1234' }, { elements: [ @@ -225,8 +203,7 @@ describe('LibraryPanelsSearch', () => { } ); - await userEvent.click(screen.getByRole('combobox', { name: /folder filter/i })); - await userEvent.type(screen.getByRole('combobox', { name: /folder filter/i }), 'library', { + await user.type(screen.getByPlaceholderText('Filter by folder'), 'library', { skipClick: true, }); @@ -329,7 +306,7 @@ describe('LibraryPanelsSearch', () => { describe('when mounted with showSecondaryActions and a specific folder', () => { describe('and user deletes a panel', () => { it('should call api with correct params', async () => { - const { getLibraryPanelsSpy } = await getTestContext( + const { user } = await getTestContext( { showSecondaryActions: true, currentFolderUID: 'wfTJJL5Wz' }, { elements: [ @@ -358,11 +335,11 @@ describe('LibraryPanelsSearch', () => { } ); - await userEvent.click(screen.getByLabelText('Delete')); - await waitFor(() => expect(screen.getByText('Do you want to delete this panel?')).toBeInTheDocument()); - await userEvent.click(screen.getAllByRole('button', { name: 'Delete' })[1]); + await user.click(screen.getByLabelText('Delete')); + await screen.findByText('Do you want to delete this panel?'); + await user.click(screen.getAllByRole('button', { name: 'Delete' })[1]); - await waitFor(() => { + await waitFor(() => expect(getLibraryPanelsSpy).toHaveBeenCalledWith({ searchString: '', folderFilterUIDs: ['wfTJJL5Wz'], @@ -370,8 +347,8 @@ describe('LibraryPanelsSearch', () => { typeFilter: [], sortDirection: undefined, perPage: 40, - }); - }); + }) + ); }); }); }); diff --git a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.tsx b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.tsx index 151752b2ede..27a6635fd3f 100644 --- a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.tsx @@ -5,7 +5,6 @@ import { useDebounce } from 'react-use'; import { GrafanaTheme2, PanelPluginMeta, SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; import { useStyles2, Stack, FilterInput } from '@grafana/ui'; -import { FolderInfo } from 'app/types/folders'; import { FolderFilter } from '../../../../core/components/FolderFilter/FolderFilter'; import { PanelTypeFilter } from '../../../../core/components/PanelTypeFilter/PanelTypeFilter'; @@ -162,7 +161,7 @@ const SearchControls = memo( [onPanelFilterChange] ); const folderFilterChanged = useCallback( - (folders: FolderInfo[]) => onFolderFilterChange(folders.map((f) => f.uid ?? '')), + (folders: string[]) => onFolderFilterChange(folders), [onFolderFilterChange] ); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index c458e08fe59..e8c29e9ffe7 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -7658,7 +7658,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "No folders found", "select-aria-label": "Folder filter", "select-placeholder": "Filter by folder" }, From 1d0ab617e8ffe9591d2098cf9aa3490d6f6712d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Irene=20Rodr=C3=ADguez?= Date: Wed, 29 Oct 2025 13:38:49 +0100 Subject: [PATCH 04/72] Update Git Sync and File provisioning status to private preview (#113158) --- docs/sources/observability-as-code/_index.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/observability-as-code/_index.md b/docs/sources/observability-as-code/_index.md index 42303dd62f9..33a2c6aeb4d 100644 --- a/docs/sources/observability-as-code/_index.md +++ b/docs/sources/observability-as-code/_index.md @@ -12,6 +12,7 @@ labels: products: - enterprise - oss + - cloud title: Observability as Code weight: 100 cards: @@ -32,11 +33,11 @@ cards: height: 24 href: ./foundation-sdk/ description: The Grafana Foundation SDK is a set of tools, types, and libraries that let you define Grafana dashboards and resources using strongly typed code. - - title: Git Sync (experimental) + - title: Git Sync (private preview) height: 24 href: ./provision-resources/intro-git-sync/ description: Git Sync is an experimental feature that lets you store your dashboard files in a GitHub repository and synchronize those changes with your Grafana instance. - - title: File provisioning (experimental) + - title: File provisioning (private preview) height: 24 href: ./provision-resources/ description: File provisioning in Grafana lets you include resources, including folders and dashboard JSON files, that are stored in a local file system. From 86bf99aaaa7ef49fff41ad76ca79ea0321e613ed Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:58:13 +0100 Subject: [PATCH 05/72] docs(alerting): add additional migration details (#112383) --- docs/sources/alerting/alerting-rules/alerting-migration.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/sources/alerting/alerting-rules/alerting-migration.md b/docs/sources/alerting/alerting-rules/alerting-migration.md index 2e63db00136..3afa3aec453 100644 --- a/docs/sources/alerting/alerting-rules/alerting-migration.md +++ b/docs/sources/alerting/alerting-rules/alerting-migration.md @@ -65,6 +65,10 @@ The copied rules are converted to Grafana-managed rules, preserving their behavi The rule query offset is taken from the `query_offset` value in the rule group configuration. If empty, it defaults to the [`rule_query_offset` configuration setting](ref:configure-grafana-rule_query_offset), which is `1m` by default. +- **Rule query conversion** + + For alert rules, adds `prometheus_math` and `threshold` expressions to preserve Prometheus no data behavior, ensuring the alert stays in **Normal** state when `query` returns no data. + - **Missing series evaluations to resolve** The [Missing series evaluations to resolve](ref:missing_series_evaluations_to_resolve) setting is set to `1` to replicate Prometheus’s alert eviction behavior. From 8bff09b88bf9363571e8a4e450d3137e4f9ca888 Mon Sep 17 00:00:00 2001 From: Luminessa Starlight Date: Wed, 29 Oct 2025 09:11:23 -0400 Subject: [PATCH 06/72] Docs: Add storybook links to components (#113102) * for every storybook component, a storybook link * typo fix * text improvements --- .../grafana-ui/src/components/Alert/Alert.tsx | 5 +++ .../AutoSaveField/AutoSaveField.tsx | 6 ++++ .../grafana-ui/src/components/Badge/Badge.tsx | 5 +++ .../src/components/BarGauge/BarGauge.tsx | 3 ++ .../src/components/BigValue/BigValue.tsx | 5 +++ .../src/components/Button/Button.tsx | 3 ++ .../ButtonCascader/ButtonCascader.tsx | 3 ++ .../CallToActionCard/CallToActionCard.tsx | 6 +++- .../grafana-ui/src/components/Card/Card.tsx | 1 + .../src/components/Carousel/Carousel.tsx | 5 +++ .../src/components/Cascader/Cascader.tsx | 5 +++ .../ClickOutsideWrapper.tsx | 5 +++ .../ClipboardButton/ClipboardButton.tsx | 5 +++ .../Collapse/CollapsableSection.tsx | 5 +++ .../src/components/Collapse/Collapse.tsx | 5 +++ .../components/ColorPicker/ColorPicker.tsx | 3 ++ .../ColorPicker/ColorPickerInput.tsx | 3 ++ .../ColorPicker/SeriesColorPickerPopover.tsx | 3 ++ .../src/components/Combobox/Combobox.tsx | 4 ++- .../src/components/Combobox/MultiCombobox.tsx | 5 +++ .../ConfirmButton/ConfirmButton.tsx | 5 +++ .../components/ConfirmModal/ConfirmModal.tsx | 5 +++ .../components/ContextMenu/ContextMenu.tsx | 5 +++ .../DataSourceHttpSettings.mdx | 2 ++ .../DataSourceHttpSettings.tsx | 2 ++ .../DateTimePickers/DatePicker/DatePicker.tsx | 7 +++- .../DatePickerWithInput.tsx | 7 +++- .../DateTimePicker/DateTimePicker.tsx | 5 +++ .../RelativeTimeRangePicker.tsx | 1 + .../DateTimePickers/TimeRangeInput.tsx | 5 +++ .../DateTimePickers/TimeRangePicker.tsx | 3 ++ .../DateTimePickers/TimeZonePicker.tsx | 3 ++ .../DateTimePickers/WeekStartPicker.tsx | 3 ++ .../src/components/Divider/Divider.tsx | 3 ++ .../src/components/Drawer/Drawer.tsx | 5 +++ .../src/components/Dropdown/ButtonSelect.tsx | 2 ++ .../src/components/Dropdown/Dropdown.tsx | 5 +++ .../EmptySearchResult/EmptySearchResult.tsx | 6 +++- .../src/components/EmptyState/EmptyState.tsx | 5 +++ .../ErrorBoundary/ErrorBoundary.tsx | 5 +++ .../components/FeatureBadge/FeatureBadge.tsx | 5 +++ .../components/FileDropzone/FileDropzone.tsx | 5 +++ .../components/FileDropzone/FileListItem.tsx | 5 +++ .../src/components/FileUpload/FileUpload.tsx | 5 +++ .../src/components/FilterPill/FilterPill.tsx | 5 +++ .../src/components/FormField/FormField.tsx | 2 ++ .../FormattedValueDisplay.tsx | 5 +++ .../src/components/Forms/Checkbox.tsx | 3 ++ .../grafana-ui/src/components/Forms/Field.tsx | 5 +++ .../src/components/Forms/FieldArray.tsx | 2 ++ .../src/components/Forms/FieldSet.tsx | 5 +++ .../Forms/FieldValidationMessage.tsx | 5 +++ .../grafana-ui/src/components/Forms/Form.tsx | 2 ++ .../src/components/Forms/InlineField.mdx | 2 +- .../src/components/Forms/InlineField.tsx | 5 +++ .../src/components/Forms/InlineFieldRow.tsx | 5 +++ .../src/components/Forms/InlineLabel.mdx | 2 +- .../src/components/Forms/InlineLabel.tsx | 5 +++ .../grafana-ui/src/components/Forms/Label.tsx | 5 +++ .../src/components/Forms/Legend.tsx | 5 +++ .../RadioButtonGroup/RadioButtonGroup.tsx | 5 +++ .../Forms/RadioButtonList/RadioButtonList.tsx | 5 +++ .../grafana-ui/src/components/Icon/Icon.tsx | 5 +++ .../src/components/IconButton/IconButton.tsx | 5 +++ .../src/components/InfoBox/InfoBox.tsx | 6 +++- .../components/InfoTooltip/InfoTooltip.tsx | 6 +++- .../components/InlineToast/InlineToast.tsx | 5 +++ .../src/components/Input/AutoSizeInput.tsx | 5 +++ .../grafana-ui/src/components/Input/Input.tsx | 5 +++ .../InteractiveTable/InteractiveTable.tsx | 8 ++++- .../src/components/Layout/Box/Box.tsx | 5 +++ .../src/components/Layout/Grid/Grid.tsx | 5 +++ .../src/components/Layout/Layout.tsx | 2 ++ .../src/components/Layout/Space.mdx | 2 +- .../src/components/Layout/Space.tsx | 5 +++ .../src/components/Layout/Stack/Stack.tsx | 3 +- .../src/components/Link/TextLink.tsx | 5 +++ .../grafana-ui/src/components/List/List.tsx | 6 +++- .../src/components/LoadingBar/LoadingBar.tsx | 5 +++ .../LoadingPlaceholder/LoadingPlaceholder.tsx | 3 ++ .../grafana-ui/src/components/Menu/Menu.tsx | 3 ++ .../grafana-ui/src/components/Modal/Modal.tsx | 3 ++ .../src/components/Monaco/CodeEditor.tsx | 5 +++ .../src/components/PageLayout/PageToolbar.tsx | 6 +++- .../src/components/Pagination/Pagination.tsx | 5 +++ .../components/PanelChrome/PanelChrome.tsx | 4 +++ .../PanelContainer/PanelContainer.tsx | 6 +++- .../PluginSignatureBadge.tsx | 2 ++ .../src/components/QueryField/QueryField.tsx | 16 +++++---- .../components/RadialGauge/RadialGauge.tsx | 3 ++ .../RefreshPicker/RefreshPicker.tsx | 5 +++ .../RenderUserContentAsHTML.tsx | 5 +++ .../ScrollContainer/ScrollContainer.tsx | 5 +++ .../SecretFormField/SecretFormField.tsx | 2 ++ .../components/SecretInput/SecretInput.tsx | 5 +++ .../SecretTextArea/SecretTextArea.tsx | 2 ++ .../src/components/Segment/Segment.tsx | 3 ++ .../src/components/Segment/SegmentAsync.tsx | 3 ++ .../src/components/Segment/SegmentInput.tsx | 3 ++ .../src/components/Select/Select.tsx | 34 ++++++++++++++++--- .../src/components/Slider/RangeSlider.tsx | 2 ++ .../src/components/Slider/Slider.tsx | 2 ++ .../src/components/Spinner/Spinner.tsx | 4 +++ .../src/components/Splitter/useSplitter.ts | 5 +++ .../src/components/Switch/Switch.tsx | 5 +++ .../src/components/Table/TableRT/Table.tsx | 5 +++ .../TableInputCSV/TableInputCSV.tsx | 6 +++- .../grafana-ui/src/components/Tabs/Tab.tsx | 3 ++ .../src/components/Tabs/TabContent.tsx | 3 ++ .../src/components/Tabs/TabsBar.tsx | 5 +++ .../grafana-ui/src/components/Tags/Tag.tsx | 5 +++ .../src/components/Tags/TagList.tsx | 5 +++ .../src/components/TagsInput/TagsInput.tsx | 5 +++ .../grafana-ui/src/components/Text/Text.tsx | 5 +++ .../src/components/TextArea/TextArea.tsx | 5 +++ .../src/components/Toggletip/Toggletip.tsx | 5 +++ .../ToolbarButton/ToolbarButton.tsx | 5 +++ .../ToolbarButton/ToolbarButtonRow.tsx | 5 +++ .../src/components/Tooltip/Tooltip.tsx | 3 ++ .../src/components/UnitPicker/UnitPicker.tsx | 3 ++ .../components/UsersIndicator/UserIcon.tsx | 5 +++ .../UsersIndicator/UsersIndicator.tsx | 6 ++++ .../components/ValuePicker/ValuePicker.tsx | 5 +++ .../src/components/VizLayout/VizLayout.tsx | 2 ++ .../src/components/VizLegend/VizLegend.tsx | 2 ++ 125 files changed, 548 insertions(+), 26 deletions(-) diff --git a/packages/grafana-ui/src/components/Alert/Alert.tsx b/packages/grafana-ui/src/components/Alert/Alert.tsx index 56b81d14351..8b3b55a41cc 100644 --- a/packages/grafana-ui/src/components/Alert/Alert.tsx +++ b/packages/grafana-ui/src/components/Alert/Alert.tsx @@ -26,6 +26,11 @@ export interface Props extends HTMLAttributes { topSpacing?: number; } +/** + * An alert displays an important message in a way that attracts the user's attention without interrupting the user's task. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-alert--docs + */ export const Alert = React.forwardRef( ( { diff --git a/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.tsx b/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.tsx index f53670b73a6..f78262d0548 100644 --- a/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.tsx +++ b/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.tsx @@ -21,6 +21,12 @@ export interface Props extends Omit { /** Input that will save its value on change */ children: (onChange: (newValue: T) => void) => React.ReactElement; } + +/** + * Used for form inputs that should save its content automatically. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-autosavefield--docs + */ export function AutoSaveField(props: Props) { const { invalid, diff --git a/packages/grafana-ui/src/components/Badge/Badge.tsx b/packages/grafana-ui/src/components/Badge/Badge.tsx index 201957ab534..53bcabc646d 100644 --- a/packages/grafana-ui/src/components/Badge/Badge.tsx +++ b/packages/grafana-ui/src/components/Badge/Badge.tsx @@ -47,6 +47,11 @@ const BadgeSkeleton: SkeletonComponent = ({ rootProps }) => { return ; }; +/** + * The badge component adds meta information to other content, for example about release status or new elements. You can add any `Icon` component or use the badge without an icon. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-badge--docs + */ export const Badge = attachSkeleton(BadgeComponent, BadgeSkeleton); const getSkeletonStyles = () => ({ diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx index 3392f059662..639bf41ca13 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx @@ -56,6 +56,9 @@ export interface Props extends Themeable2 { isOverflow: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-bargauge--docs + */ export class BarGauge extends PureComponent { static defaultProps: Partial = { lcdCellWidth: 12, diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.tsx index 08bacf3e2af..870d350ba95 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValue.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValue.tsx @@ -82,6 +82,11 @@ export interface Props extends Themeable2 { disableWideLayout?: boolean; } +/** + * Component for showing a value based on a [DisplayValue](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/displayValue.ts#L5). + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-bigvalue--docs + */ export const BigValue = memo((props) => { const { onClick, className, hasLinks, theme, justifyMode = BigValueJustifyMode.Auto } = props; diff --git a/packages/grafana-ui/src/components/Button/Button.tsx b/packages/grafana-ui/src/components/Button/Button.tsx index c596904a39b..9e1d1c4a1a9 100644 --- a/packages/grafana-ui/src/components/Button/Button.tsx +++ b/packages/grafana-ui/src/components/Button/Button.tsx @@ -51,6 +51,9 @@ type CommonProps = BasePropsWithChildren | NoChildrenTooltip | NoChildrenAriaLab export type ButtonProps = CommonProps & ButtonHTMLAttributes; +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-button--docs + */ export const Button = React.forwardRef( ( { diff --git a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx index 43aaff2bee2..e52b368f759 100644 --- a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx +++ b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx @@ -28,6 +28,9 @@ export interface ButtonCascaderProps { hideDownIcon?: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-buttoncascader--docs + */ export const ButtonCascader = (props: ButtonCascaderProps) => { const { onChange, className, loadData, icon, buttonProps, hideDownIcon, variant, disabled, ...rest } = props; const styles = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx index ca3348c2b54..cd859f77256 100644 --- a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx +++ b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx @@ -11,7 +11,11 @@ export interface CallToActionCardProps { className?: string; } -/** @deprecated Use instead */ +/** + * @deprecated Use `` instead. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-deprecated-calltoactioncard--docs + */ export const CallToActionCard = ({ message, callToActionElement, footer, className }: CallToActionCardProps) => { const css = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/Card/Card.tsx b/packages/grafana-ui/src/components/Card/Card.tsx index 80194b56136..f1514f6f60e 100644 --- a/packages/grafana-ui/src/components/Card/Card.tsx +++ b/packages/grafana-ui/src/components/Card/Card.tsx @@ -51,6 +51,7 @@ const CardContext = React.createContext<{ /** * Generic card component * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-card--docs * @public */ export const Card: CardInterface = ({ diff --git a/packages/grafana-ui/src/components/Carousel/Carousel.tsx b/packages/grafana-ui/src/components/Carousel/Carousel.tsx index f3cc7892293..57f4ed59005 100644 --- a/packages/grafana-ui/src/components/Carousel/Carousel.tsx +++ b/packages/grafana-ui/src/components/Carousel/Carousel.tsx @@ -22,6 +22,11 @@ export interface CarouselProps { images: CarouselImage[]; } +/** + * The Carousel component displays a grid of image thumbnails that can be clicked to view full-sized images in a modal with navigation controls. It provides an elegant way to present collections of images or screenshots with fullscreen preview capabilities. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-carousel--docs + */ export const Carousel: React.FC = ({ images }) => { const [selectedIndex, setSelectedIndex] = useState(null); const [imageErrors, setImageErrors] = useState>({}); diff --git a/packages/grafana-ui/src/components/Cascader/Cascader.tsx b/packages/grafana-ui/src/components/Cascader/Cascader.tsx index ed8c1af953d..8f80b3b6093 100644 --- a/packages/grafana-ui/src/components/Cascader/Cascader.tsx +++ b/packages/grafana-ui/src/components/Cascader/Cascader.tsx @@ -321,4 +321,9 @@ class UnthemedCascader extends PureComponent { } } +/** + * The cascader component is a Select with a cascading flyout menu. When you have lots of options in your select, they can be hard to navigate from a regular dropdown list. In that case you can use the cascader to organize your options into groups hierarchically. Just like in the Select component, the cascader input doubles as a search field to quickly jump to a selection without navigating the list. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-cascader--docs + */ export const Cascader = withTheme2(UnthemedCascader); diff --git a/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx b/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx index 4a3b9e9b88a..1e9ab3e6de1 100644 --- a/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx +++ b/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx @@ -13,6 +13,11 @@ export interface Props { children: React.ReactNode; } +/** + * A wrapper component that detects clicks outside of the elements by attaching event listener to `window` or `document` objects. Useful for components that require an action being triggered when a click outside has occurred, for example closing an overlay or popup. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/utilities-clickoutsidewrapper--docs + */ export function ClickOutsideWrapper({ includeButtonPress = true, parent = window, diff --git a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx index a42871dcde0..f1f00cdb25e 100644 --- a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx +++ b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx @@ -21,6 +21,11 @@ export type Props = ButtonProps & { const SHOW_SUCCESS_DURATION = 2 * 1000; +/** + * A control for allowing the user to copy text to their clipboard. Uses native APIs on modern browsers, falling back to the old `document.execCommand('copy')` API on other browsers. The text to be copied should be provided via `getText` prop. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-clipboardbutton--docs + */ export function ClipboardButton({ onClipboardCopy, onClipboardError, diff --git a/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx index 12c00333dfd..936d9837813 100644 --- a/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx +++ b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx @@ -25,6 +25,11 @@ export interface Props { unmountContentWhenClosed?: boolean; } +/** + * A simple container for enabling collapsing/expanding of content. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-collapsablesection--docs + */ export const CollapsableSection = ({ label, isOpen, diff --git a/packages/grafana-ui/src/components/Collapse/Collapse.tsx b/packages/grafana-ui/src/components/Collapse/Collapse.tsx index 57aec7a3ed4..d0f8757e1ca 100644 --- a/packages/grafana-ui/src/components/Collapse/Collapse.tsx +++ b/packages/grafana-ui/src/components/Collapse/Collapse.tsx @@ -128,6 +128,11 @@ export const ControlledCollapse = ({ isOpen, onToggle, ...otherProps }: React.Pr ); }; +/** + * A content area, which can be horizontally collapsed and expanded. Can be used to hide extra information on the page. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-collapse--docs + */ export const Collapse = ({ isOpen, label, diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index 819d60746db..f44b3f00fa4 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -84,6 +84,9 @@ export const colorPickerFactory = ( }; }; +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/pickers-colorpicker--docs + */ export const ColorPicker = withTheme2(colorPickerFactory(ColorPickerPopover, 'ColorPicker')); export const SeriesColorPicker = withTheme2(colorPickerFactory(SeriesColorPickerPopover, 'SeriesColorPicker')); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx index 27f807051a8..5dd4e2acb25 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx @@ -19,6 +19,9 @@ export interface ColorPickerInputProps extends Omit( ({ value = '', onChange, returnColorAs = 'rgb', ...inputProps }, ref) => { const [currentColor, setColor] = useState(value); diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index 2c0350d9329..6e38fa3148e 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -12,6 +12,9 @@ export interface SeriesColorPickerPopoverProps extends ColorPickerProps, Popover onToggleAxis?: () => void; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/pickers-seriescolorpicker--docs + */ export const SeriesColorPickerPopover = (props: SeriesColorPickerPopoverProps) => { const { yaxis, onToggleAxis, color, ...colorPickerProps } = props; const yAxisLabel = t('grafana-ui.series-color-picker-popover.y-axis-usage', 'Use right y-axis'); diff --git a/packages/grafana-ui/src/components/Combobox/Combobox.tsx b/packages/grafana-ui/src/components/Combobox/Combobox.tsx index 9c4de9c2d8b..a4664c62441 100644 --- a/packages/grafana-ui/src/components/Combobox/Combobox.tsx +++ b/packages/grafana-ui/src/components/Combobox/Combobox.tsx @@ -119,8 +119,10 @@ const noop = () => {}; export const VIRTUAL_OVERSCAN_ITEMS = 4; /** - * A performant Select replacement. + * A performant and accessible combobox component that supports both synchronous and asynchronous options loading. It provides type-ahead filtering, keyboard navigation, and virtual scrolling for handling large datasets efficiently. + * Replaces the Select component, and has better performance. * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-combobox--docs * @alpha */ export const Combobox = (props: ComboboxProps) => { diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx index 989bf3d0e2e..3a1e2856a68 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx @@ -35,6 +35,11 @@ interface MultiComboboxBaseProps export type MultiComboboxProps = MultiComboboxBaseProps & AutoSizeConditionals; +/** + * The behavior of the MultiCombobox is similar to that of the Combobox, but it allows you to select multiple options. For all non-multi behaviors, see the Combobox documentation. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-multicombobox--docs + */ export const MultiCombobox = (props: MultiComboboxProps) => { const { placeholder, diff --git a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx index 5ddfd473700..0a5e3db8688 100644 --- a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx +++ b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx @@ -31,6 +31,11 @@ export interface Props { onCancel?(): void; } +/** + * The ConfirmButton is an interactive component that adds a double-confirm option to a clickable action. When clicked, the action is replaced by an inline confirmation with the option to cancel. In Grafana, this is used, for example, for editing values in settings tables. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-confirmbutton--docs + */ export const ConfirmButton = ({ children, className, diff --git a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx index ffcffa96f58..b57f9d8557b 100644 --- a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx +++ b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx @@ -48,6 +48,11 @@ export interface ConfirmModalProps { disabled?: boolean; } +/** + * Used to request user for action confirmation, e.g. deleting items. Triggers provided `onConfirm` callback. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-confirmmodal--docs + */ export const ConfirmModal = ({ isOpen, title, diff --git a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx index 86802d58743..5a96497cdef 100644 --- a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx +++ b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx @@ -22,6 +22,11 @@ export interface ContextMenuProps { renderHeader?: () => React.ReactNode; } +/** + * A menu displaying additional options when it's not possible to show them at all times due to a space constraint. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-contextmenu--docs + */ export const ContextMenu = React.memo( ({ x, y, onClose, focusOnOpen = true, renderMenuItems, renderHeader }: ContextMenuProps) => { const menuRef = useRef(null); diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx index 08671fcb108..f241ced1384 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx +++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx @@ -5,6 +5,8 @@ import { DataSourceHttpSettings } from './DataSourceHttpSettings'; # DataSourceHttpSettings +> **Deprecated!** Use components from `@grafana/plugin-ui` instead, according to the [migration guide](https://github.com/grafana/plugin-ui/blob/main/src/components/ConfigEditor/migrating-from-datasource-http-settings.md) + Component for displaying the configuration options for a data source plugin. ### When to use diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx index e57f7747494..65f6ff77259 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx @@ -72,6 +72,8 @@ const LABEL_WIDTH = 26; /** * @deprecated Use components from `@grafana/plugin-ui` instead, according to the [migration guide](https://github.com/grafana/plugin-ui/blob/main/src/components/ConfigEditor/migrating-from-datasource-http-settings.md). + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-datasourcehttpsettings--docs */ export const DataSourceHttpSettings = (props: HttpSettingsProps) => { const { diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx index 28c88547023..a69a6d9fb18 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx @@ -19,7 +19,12 @@ export interface DatePickerProps { maxDate?: Date; } -/** @public */ +/** + * A component with a calendar view for selecting a date. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-datepicker--docs + * @public + * */ export const DatePicker = memo((props) => { const styles = useStyles2(getStyles); const { isOpen, onClose } = props; diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx index 4d2d6533a31..0103a1d21a5 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx @@ -27,7 +27,12 @@ export interface DatePickerWithInputProps extends Omit( ({ value, minDate, maxDate, onChange, closeOnSelect, placeholder = 'Date', ...rest }, ref) => { const [open, setOpen] = useState(false); diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx index 1f4eb4da47d..a7024681d42 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx @@ -60,6 +60,11 @@ export interface Props { timeZone?: TimeZone; } +/** + * A component for selecting a date *and* time. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-datetimepicker--docs + */ export const DateTimePicker = ({ date, maxDate, diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx index 74a19dd6efd..e07345bf111 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx @@ -41,6 +41,7 @@ type InputState = { }; /** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-relativetimerangepicker--docs * @internal */ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx index 67d3af9496a..90666dad8b3 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx @@ -36,6 +36,11 @@ export interface TimeRangeInputProps { const noop = () => {}; +/** + * A variant of TimeRangePicker for use in forms. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-timerangeinput--docs + */ export const TimeRangeInput = ({ value, onChange, diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx index 3157b6ca837..b26a6e4c36d 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx @@ -73,6 +73,9 @@ export interface State { isOpen: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-timerangepicker--docs + */ export function TimeRangePicker(props: TimeRangePickerProps) { const [isOpen, setOpen] = useState(false); diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx index f41279cb983..464dcc9fb24 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx @@ -31,6 +31,9 @@ export interface Props { openMenuOnFocus?: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-timezonepicker--docs + */ export const TimeZonePicker = (props: Props) => { const { onChange, diff --git a/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx index 0207dcb67a8..2fe37d06d3f 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx @@ -39,6 +39,9 @@ export function getWeekStart(override?: string): WeekStart { return 'monday'; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-weekstartpicker--docs + */ export const WeekStartPicker = (props: Props) => { const { onChange, width, autoFocus = false, onBlur, value, disabled = false, inputId } = props; const weekStarts: ComboboxOption[] = useMemo( diff --git a/packages/grafana-ui/src/components/Divider/Divider.tsx b/packages/grafana-ui/src/components/Divider/Divider.tsx index b9f5f580183..721ea8b58dc 100644 --- a/packages/grafana-ui/src/components/Divider/Divider.tsx +++ b/packages/grafana-ui/src/components/Divider/Divider.tsx @@ -9,6 +9,9 @@ interface DividerProps { spacing?: ThemeSpacingTokens; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-divider--docs + */ export const Divider = ({ direction = 'horizontal', spacing = 2 }: DividerProps) => { const styles = useStyles2(getStyles, spacing); diff --git a/packages/grafana-ui/src/components/Drawer/Drawer.tsx b/packages/grafana-ui/src/components/Drawer/Drawer.tsx index b81a3784b72..b7ee0252bfc 100644 --- a/packages/grafana-ui/src/components/Drawer/Drawer.tsx +++ b/packages/grafana-ui/src/components/Drawer/Drawer.tsx @@ -62,6 +62,11 @@ const drawerSizes = { lg: { width: '75vw', minWidth: 744 }, }; +/** + * Drawer is a slide in overlay that can be used to display additional information without hiding the main page content. It can be anchored to the left or right edge of the screen. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-drawer--docs + */ export function Drawer({ children, onClose, diff --git a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx index a2e23e78575..99f4db3b56b 100644 --- a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx +++ b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx @@ -25,6 +25,8 @@ export interface Props extends HTMLAttributes { /** * @deprecated Use Combobox or Dropdown instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-buttonselect--docs */ const ButtonSelectComponent = (props: Props) => { const { className, options, value, onChange, narrow, variant, root, ...restProps } = props; diff --git a/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx index 77cdcc92519..b098665db7b 100644 --- a/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx +++ b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx @@ -31,6 +31,11 @@ export interface Props { onVisibleChange?: (state: boolean) => void; } +/** + * Hook up a menu or other overlay to any trigger. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-dropdown--docs + */ export const Dropdown = React.memo(({ children, overlay, placement, offset, root, onVisibleChange }: Props) => { const [show, setShow] = useState(false); const transitionRef = useRef(null); diff --git a/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx b/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx index 2ddafbf865b..72f353b4ed7 100644 --- a/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx +++ b/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx @@ -8,7 +8,11 @@ export interface Props { children: JSX.Element | string; } -/** @deprecated Use instead */ +/** + * @deprecated Use `` instead. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-deprecated-emptysearchresult--docs + */ const EmptySearchResult = ({ children }: Props) => { const styles = useStyles2(getStyles); return
{children}
; diff --git a/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx b/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx index a7ee3774666..7e9c73fb0f1 100644 --- a/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx +++ b/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx @@ -38,6 +38,11 @@ interface Props { role?: AriaRole; } +/** + * The EmptyState component consists of a message and optionally an image, button, and additional information. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-emptystate--docs + */ export const EmptyState = ({ button, children, diff --git a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx index 63044d76d2f..dd49f34b985 100644 --- a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -33,6 +33,11 @@ interface State { errorInfo: ErrorInfo | null; } +/** + * A React component that catches errors in child components. Useful for logging or displaying a fallback UI in case of errors. More information about error boundaries is available at [React documentation website](https://reactjs.org/docs/error-boundaries.html). + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/utilities-errorboundary--docs + */ export class ErrorBoundary extends PureComponent { readonly state: State = { error: null, diff --git a/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx b/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx index 6a67913e453..f1af47396f3 100644 --- a/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx +++ b/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx @@ -8,6 +8,11 @@ export interface FeatureBadgeProps { tooltip?: string; } +/** + * A component for displaying information about different release stages of features, in accordance with the guidelines provided at [Grafana's Release Life Cycle](https://grafana.com/docs/release-life-cycle). + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-featurebadge--docs + */ export const FeatureBadge = ({ featureState, tooltip }: FeatureBadgeProps) => { const display = getPanelStateBadgeDisplayModel(featureState); return ; diff --git a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx index 120c7e08a09..ad1fa0b8b49 100644 --- a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx +++ b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx @@ -63,6 +63,11 @@ export interface DropzoneFile { retryUpload?: () => void; } +/** + * A dropzone component to use for file uploads. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-filedropzone--docs + */ export function FileDropzone({ options, children, diff --git a/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx b/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx index b13066621c6..e54d66b34b5 100644 --- a/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx +++ b/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx @@ -17,6 +17,11 @@ export interface FileListItemProps { removeFile?: (file: DropzoneFile) => void; } +/** + * A FileListItem component used for the FileDropzone component to show uploaded files. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-filelistitem--docs + */ export function FileListItem({ file: customFile, removeFile }: FileListItemProps) { const styles = useStyles2(getStyles); const { file, progress, error, abortUpload, retryUpload } = customFile; diff --git a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx index 528d4a7e193..5ad7662ff56 100644 --- a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx +++ b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx @@ -27,6 +27,11 @@ export interface Props { showFileName?: boolean; } +/** + * A button-styled input that triggers file upload popup. Button text and accepted file extensions can be customized via `label` and `accepted` props respectively. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-fileupload--docs + */ export const FileUpload = ({ onFileUpload, className, diff --git a/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx b/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx index 48140e3940d..6fdbaa2e76d 100644 --- a/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx +++ b/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx @@ -15,6 +15,11 @@ export interface FilterPillProps { icon?: IconName; } +/** + * A component used for quick toggling on/off filters. Mostly used in inline form components and transformation/query editors. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-filterpill--docs + */ export const FilterPill = ({ label, selected, onClick, icon = 'check' }: FilterPillProps) => { const styles = useStyles2(getStyles); const clearButton = useStyles2(clearButtonStyles); diff --git a/packages/grafana-ui/src/components/FormField/FormField.tsx b/packages/grafana-ui/src/components/FormField/FormField.tsx index 4d9bca9ded8..c4663db842e 100644 --- a/packages/grafana-ui/src/components/FormField/FormField.tsx +++ b/packages/grafana-ui/src/components/FormField/FormField.tsx @@ -24,6 +24,8 @@ export interface Props extends InputHTMLAttributes { * * For inline fields, use {@link InlineField}, {@link https://developers.grafana.com/ui/latest/index.html?path=/story/forms-inlinefield--basic See Storybook}. * @deprecated Please use the {@link Field} component, {@link https://developers.grafana.com/ui/latest/index.html?path=/story/forms-field--simple See Storybook}. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-deprecated-formfield--docs */ export const FormField = ({ label, diff --git a/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx b/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx index 6c9d6dd5334..f2e0527939a 100644 --- a/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx +++ b/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx @@ -18,6 +18,11 @@ function fontSizeReductionFactor(fontSize: number) { return 0.6; } +/** + * Used to display a value, which also supports prefix and suffix. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-formattedvaluedisplay--docs + */ export const FormattedValueDisplay = ({ value, className, style, ...htmlProps }: Props) => { const hasPrefix = (value.prefix ?? '').length > 0; const hasSuffix = (value.suffix ?? '').length > 0; diff --git a/packages/grafana-ui/src/components/Forms/Checkbox.tsx b/packages/grafana-ui/src/components/Forms/Checkbox.tsx index 173f821e287..c20b0892e21 100644 --- a/packages/grafana-ui/src/components/Forms/Checkbox.tsx +++ b/packages/grafana-ui/src/components/Forms/Checkbox.tsx @@ -24,6 +24,9 @@ export interface CheckboxProps extends Omit, 'value' invalid?: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-checkbox--docs + */ export const Checkbox = React.forwardRef( ( { label, description, value, htmlValue, onChange, disabled, className, indeterminate, invalid, ...inputProps }, diff --git a/packages/grafana-ui/src/components/Forms/Field.tsx b/packages/grafana-ui/src/components/Forms/Field.tsx index 1c80a2caf81..6c5884e04c9 100644 --- a/packages/grafana-ui/src/components/Forms/Field.tsx +++ b/packages/grafana-ui/src/components/Forms/Field.tsx @@ -43,6 +43,11 @@ export interface FieldProps extends HTMLAttributes { noMargin?: boolean; } +/** + * Field is the basic component for rendering form elements together with labels and description. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-field--docs + */ export const Field = React.forwardRef( ( { diff --git a/packages/grafana-ui/src/components/Forms/FieldArray.tsx b/packages/grafana-ui/src/components/Forms/FieldArray.tsx index fc0cda3b7ff..5c21767ab84 100644 --- a/packages/grafana-ui/src/components/Forms/FieldArray.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldArray.tsx @@ -9,6 +9,8 @@ export interface FieldArrayProps extends UseFieldArrayProps { /** * @deprecated use the `useFieldArray` hook from react-hook-form instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-fieldarray--docs */ export const FieldArray: FC = ({ name, control, children, ...rest }) => { const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({ diff --git a/packages/grafana-ui/src/components/Forms/FieldSet.tsx b/packages/grafana-ui/src/components/Forms/FieldSet.tsx index 421ba3a5ad3..4f42cb96f8e 100644 --- a/packages/grafana-ui/src/components/Forms/FieldSet.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldSet.tsx @@ -14,6 +14,11 @@ export interface Props extends Omit, 'label'> { label?: React.ReactNode; } +/** + * Component used to group form elements inside a form, equivalent to HTML's [fieldset](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset) tag. Accepts optional label, which, if provided, is used as a text for the set's legend. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-fieldset--docs + */ export const FieldSet = ({ label, children, className, ...rest }: Props) => { const styles = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx b/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx index 78f1aeab79e..3a3fd5b078b 100644 --- a/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx @@ -12,6 +12,11 @@ export interface FieldValidationMessageProps { horizontal?: boolean; } +/** + * Component for displaying a validation error message under an element. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-fieldvalidationmessage--docs + */ export const FieldValidationMessage = ({ children, horizontal, diff --git a/packages/grafana-ui/src/components/Forms/Form.tsx b/packages/grafana-ui/src/components/Forms/Form.tsx index 2eb45c69a0e..c251780be96 100644 --- a/packages/grafana-ui/src/components/Forms/Form.tsx +++ b/packages/grafana-ui/src/components/Forms/Form.tsx @@ -18,6 +18,8 @@ interface FormProps extends Omit({ defaultValues, diff --git a/packages/grafana-ui/src/components/Forms/InlineField.mdx b/packages/grafana-ui/src/components/Forms/InlineField.mdx index 1b13b8d3811..b08be513645 100644 --- a/packages/grafana-ui/src/components/Forms/InlineField.mdx +++ b/packages/grafana-ui/src/components/Forms/InlineField.mdx @@ -3,7 +3,7 @@ import { InlineField } from './InlineField'; # InlineField -A basic component for rendering form elements, like `Input`, `Select`, `Checkbox`, etc, inline together with `InlineLabel`. If the child element has `id` specified, the label's `htmlFor` attribute, pointing to the id, will be added. +A basic component for rendering form elements, like `Input`, `Checkbox`, `Combobox`, etc, inline together with `InlineLabel`. If the child element has `id` specified, the label's `htmlFor` attribute, pointing to the id, will be added. The width of the `InlineLabel` can be modified via `labelWidth` prop, which is a multiple of 8px. For example, an `InlineField` with `labelWidth={20}` will have a label 160px wide. diff --git a/packages/grafana-ui/src/components/Forms/InlineField.tsx b/packages/grafana-ui/src/components/Forms/InlineField.tsx index c63d87b585b..a2e7bd4f9e7 100644 --- a/packages/grafana-ui/src/components/Forms/InlineField.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineField.tsx @@ -29,6 +29,11 @@ export interface Props extends Omit, 'css'> { children: ReactNode | ReactNode[]; } +/** + * Used to align multiple InlineField components in one row. The row will wrap if the width of the children exceeds its own. Equivalent to the div with gf-form-inline class name. Multiple InlineFieldRows vertically stack on each other. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-inlinefieldrow--docs + */ export const InlineFieldRow = ({ children, className, ...htmlProps }: Props) => { const styles = useStyles2(getStyles); return ( diff --git a/packages/grafana-ui/src/components/Forms/InlineLabel.mdx b/packages/grafana-ui/src/components/Forms/InlineLabel.mdx index 9a451f07922..be5b5766a9d 100644 --- a/packages/grafana-ui/src/components/Forms/InlineLabel.mdx +++ b/packages/grafana-ui/src/components/Forms/InlineLabel.mdx @@ -3,7 +3,7 @@ import { InlineLabel } from './InlineLabel'; # InlineLabel -A horizontal variant of `Label`, primarily used in query editors. Can be combined with form components that expect a label, eg. `Input`, `Select`, `Checkbox`. +A horizontal variant of `Label`, primarily used in query editors. Can be combined with form components that expect a label, eg. `Input`, `Checkbox`, `Combobox`. If you need to add additional explanation, use the tooltip prop, which will render an info icon with tooltip inside the label. For query editor readability, the label text should be as short as possible (4 words or fewer). diff --git a/packages/grafana-ui/src/components/Forms/InlineLabel.tsx b/packages/grafana-ui/src/components/Forms/InlineLabel.tsx index fef27352c7f..63b7b7ccb46 100644 --- a/packages/grafana-ui/src/components/Forms/InlineLabel.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineLabel.tsx @@ -25,6 +25,11 @@ export interface Props extends Omit category?: React.ReactNode[]; } +/** + * The label component can be used to label form inputs with a heading/"Option name" and a description. To automatically have the right arrangement of this component with a form input, use the `Field` component. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-label--docs + */ export const Label = ({ children, description, className, category, ...labelProps }: LabelProps) => { const styles = useStyles2(getLabelStyles); const categories = category?.map((c, i) => { diff --git a/packages/grafana-ui/src/components/Forms/Legend.tsx b/packages/grafana-ui/src/components/Forms/Legend.tsx index 7b48c149b7b..90af8e1229e 100644 --- a/packages/grafana-ui/src/components/Forms/Legend.tsx +++ b/packages/grafana-ui/src/components/Forms/Legend.tsx @@ -21,6 +21,11 @@ export const getLegendStyles = (theme: GrafanaTheme2) => { }; }; +/** + * Legend should be used to add a caption to a group of related form elements that have been grouped toegheter into a `FieldSet`. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-legend--docs + */ export const Legend = ({ children, className, ...legendProps }: LabelProps) => { const styles = useStyles2(getLegendStyles); diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx index b47ff9913d3..ebef41cbed8 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx @@ -24,6 +24,11 @@ export interface RadioButtonGroupProps { invalid?: boolean; } +/** + * RadioButtonGroup is used to select a single value from multiple mutually exclusive options. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-radiobuttongroup--docs + */ export function RadioButtonGroup({ options, value, diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonList/RadioButtonList.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonList/RadioButtonList.tsx index a6366c64756..6127d143438 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonList/RadioButtonList.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonList/RadioButtonList.tsx @@ -22,6 +22,11 @@ export interface RadioButtonListProps { className?: string; } +/** + * RadioButtonList is used to select a single value from multiple mutually exclusive options usually in a vertical manner. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-radiobuttonlist--docs + */ export function RadioButtonList({ name, id, diff --git a/packages/grafana-ui/src/components/Icon/Icon.tsx b/packages/grafana-ui/src/components/Icon/Icon.tsx index ed987489f14..fa4e1fac6d3 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.tsx @@ -42,6 +42,11 @@ const getIconStyles = (theme: GrafanaTheme2) => { }; }; +/** + * Grafana's icon wrapper component. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/iconography-icon--docs + */ export const Icon = React.memo( React.forwardRef( ({ size = 'md', type = 'default', name, className, style, title = '', ...rest }, ref) => { diff --git a/packages/grafana-ui/src/components/IconButton/IconButton.tsx b/packages/grafana-ui/src/components/IconButton/IconButton.tsx index bbc4519f71f..d4d77c33fab 100644 --- a/packages/grafana-ui/src/components/IconButton/IconButton.tsx +++ b/packages/grafana-ui/src/components/IconButton/IconButton.tsx @@ -43,6 +43,11 @@ interface BasePropsWithAriaLabel extends BaseProps { export type Props = BasePropsWithTooltip | BasePropsWithAriaLabel; +/** + * This component looks just like an icon but behaves like a button. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-iconbutton--docs + */ export const IconButton = React.forwardRef((props, ref) => { const { size = 'md', variant = 'secondary' } = props; let limitedIconSize: LimitedIconSize; diff --git a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx index cb445321333..bf3e0e7f69c 100644 --- a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx +++ b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx @@ -23,7 +23,11 @@ export interface InfoBoxProps extends Omit, onDismiss?: () => void; } -/** @deprecated use Alert with severity info */ +/** + * @deprecated use Alert with severity info. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-deprecated-infobox--docs + * */ export const InfoBox = React.memo( React.forwardRef( ({ title, className, children, branded, url, urlTitle, onDismiss, severity = 'info', ...otherProps }, ref) => { diff --git a/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx b/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx index 6f189837c43..9af5cadaa74 100644 --- a/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx +++ b/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx @@ -6,7 +6,11 @@ interface InfoTooltipProps extends Omit { children: PopoverContent; } -/** @deprecated Use instead */ +/** + * @deprecated Use instead. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-deprecated-infotooltip--docs + */ export const InfoTooltip = ({ children, ...restProps }: InfoTooltipProps) => { return ; }; diff --git a/packages/grafana-ui/src/components/InlineToast/InlineToast.tsx b/packages/grafana-ui/src/components/InlineToast/InlineToast.tsx index 178b0032b1e..de79c028d9f 100644 --- a/packages/grafana-ui/src/components/InlineToast/InlineToast.tsx +++ b/packages/grafana-ui/src/components/InlineToast/InlineToast.tsx @@ -24,6 +24,11 @@ export interface InlineToastProps { alternativePlacement?: Side; } +/** + * Used to indicate temporal status near fields/components, such as a *Saved* indicator next to a field, or a little *Copied!* indicator above a button. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-inlinetoast--docs + */ export function InlineToast({ referenceElement, children, suffixIcon, placement }: InlineToastProps) { const styles = useStyles2(getStyles); const theme = useTheme2(); diff --git a/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx b/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx index 2cf17612d99..2e533cf065f 100644 --- a/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx +++ b/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx @@ -20,6 +20,11 @@ export interface Props extends InputProps { defaultValue?: string | number | readonly string[]; } +/** + * You can use it or regular text input. When used, AutoSizeInput resizes itself to the current content. For an array of data or tree-structured data, consider using `Combobox` or `Cascader` respectively. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-autosizeinput--docs + */ export const AutoSizeInput = React.forwardRef((props, ref) => { const { defaultValue = '', diff --git a/packages/grafana-ui/src/components/Input/Input.tsx b/packages/grafana-ui/src/components/Input/Input.tsx index 62aca888d03..4da6c9c22c7 100644 --- a/packages/grafana-ui/src/components/Input/Input.tsx +++ b/packages/grafana-ui/src/components/Input/Input.tsx @@ -34,6 +34,11 @@ interface StyleDeps { width?: number; } +/** + * Used for regular text input. For an array of data or tree-structured data, consider using `Combobox` or `Cascader` respectively. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-input--docs + */ export const Input = forwardRef((props, ref) => { const { className, diff --git a/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.tsx b/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.tsx index 4c0faebccf5..a046f24b89c 100644 --- a/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.tsx +++ b/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.tsx @@ -172,7 +172,13 @@ interface WithoutExpandableRow extends BaseProps = WithExpandableRow | WithoutExpandableRow; -/** @alpha */ +/** + * The InteractiveTable is used to display and select data efficiently. It allows for the display and modification of detailed information. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-interactivetable--docs + * + * @alpha + */ export function InteractiveTable({ className, columns, diff --git a/packages/grafana-ui/src/components/Layout/Box/Box.tsx b/packages/grafana-ui/src/components/Layout/Box/Box.tsx index c012eb9d5f3..a7d10e4c746 100644 --- a/packages/grafana-ui/src/components/Layout/Box/Box.tsx +++ b/packages/grafana-ui/src/components/Layout/Box/Box.tsx @@ -70,6 +70,11 @@ export interface BoxProps extends FlexProps, SizeProps, Omit; } +/** + * The Box Component is the most basic layout component. It can be used to build more complex components and layouts with properties that use our design tokens instead of using CSS. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-box--docs + */ export const Box = forwardRef>((props, ref) => { const { children, diff --git a/packages/grafana-ui/src/components/Layout/Grid/Grid.tsx b/packages/grafana-ui/src/components/Layout/Grid/Grid.tsx index 1573f881a04..ffba1eb88de 100644 --- a/packages/grafana-ui/src/components/Layout/Grid/Grid.tsx +++ b/packages/grafana-ui/src/components/Layout/Grid/Grid.tsx @@ -34,6 +34,11 @@ interface PropsWithMinColumnWidth extends GridPropsBase { /** 'columns' and 'minColumnWidth' are mutually exclusive */ type GridProps = PropsWithColumns | PropsWithMinColumnWidth; +/** + * The Grid component is a layout component that allows you to create a grid of columns and rows to organize content and elements. It is a wrapper around the [CSS Grid](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout) specification. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-grid--docs + */ export const Grid = forwardRef((props, ref) => { const { alignItems, children, gap, rowGap, columnGap, columns, minColumnWidth, ...rest } = props; const styles = useStyles2(getGridStyles, gap, rowGap, columnGap, columns, minColumnWidth, alignItems); diff --git a/packages/grafana-ui/src/components/Layout/Layout.tsx b/packages/grafana-ui/src/components/Layout/Layout.tsx index a2f34d44d06..71fe0803b83 100644 --- a/packages/grafana-ui/src/components/Layout/Layout.tsx +++ b/packages/grafana-ui/src/components/Layout/Layout.tsx @@ -33,6 +33,8 @@ export interface ContainerProps { /** * @deprecated use Stack component instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-deprecated-groups--docs */ export const Layout = ({ children, diff --git a/packages/grafana-ui/src/components/Layout/Space.mdx b/packages/grafana-ui/src/components/Layout/Space.mdx index 47bc0cb8e54..dd5182a8cea 100644 --- a/packages/grafana-ui/src/components/Layout/Space.mdx +++ b/packages/grafana-ui/src/components/Layout/Space.mdx @@ -5,7 +5,7 @@ import { Space } from './Space'; # Space -The `Space` component is a component used to add space between elements. Horizontal space is added using the `h` prop, while vertical space is added using the `v` prop. When adding horizontal space between inline or inline-block elements, the `layout` props should be set to `inline`, otherwise the `block` value of the prop can be used. +The `Space` component is a component used to add space between elements. Horizontal space is added using the `h` prop, while vertical space is added using the `v` prop. When adding horizontal space between inline or inline-block elements, the `layout` prop should be set to `inline`, otherwise the `block` value of the prop can be used. ### Usage diff --git a/packages/grafana-ui/src/components/Layout/Space.tsx b/packages/grafana-ui/src/components/Layout/Space.tsx index 2df48501ea4..8a81bcb1eed 100644 --- a/packages/grafana-ui/src/components/Layout/Space.tsx +++ b/packages/grafana-ui/src/components/Layout/Space.tsx @@ -19,6 +19,11 @@ export interface SpaceProps { layout?: 'block' | 'inline'; } +/** + * The Space component is a component used to add space between elements. Horizontal space is added using the `h` prop, while vertical space is added using the `v` prop. When adding horizontal space between inline or inline-block elements, the `layout` prop should be set to `"inline"`, otherwise the `"block"` value of the prop can be used. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-space--docs + */ export const Space = ({ v = 0, h = 0, layout }: SpaceProps) => { return ; }; diff --git a/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx b/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx index e54cc724f2d..b3a4984b110 100644 --- a/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx +++ b/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx @@ -21,7 +21,8 @@ interface StackProps extends FlexProps, SizeProps, Omit((props, ref) => { const { diff --git a/packages/grafana-ui/src/components/Link/TextLink.tsx b/packages/grafana-ui/src/components/Link/TextLink.tsx index d46a1ba3bc3..1f7ec1e16de 100644 --- a/packages/grafana-ui/src/components/Link/TextLink.tsx +++ b/packages/grafana-ui/src/components/Link/TextLink.tsx @@ -43,6 +43,11 @@ const svgSizes: { bodySmall: 'xs', }; +/** + * The TextLink component renders an anchor tag `` that takes users to another page, external or internal to Grafana. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/foundations-textlink--docs + */ export const TextLink = forwardRef( ( { href, color = 'link', external = false, inline = true, variant = 'body', weight, icon, children, ...rest }, diff --git a/packages/grafana-ui/src/components/List/List.tsx b/packages/grafana-ui/src/components/List/List.tsx index a58bb3c5d4e..d4b86807a7b 100644 --- a/packages/grafana-ui/src/components/List/List.tsx +++ b/packages/grafana-ui/src/components/List/List.tsx @@ -2,7 +2,11 @@ import { PureComponent } from 'react'; import { ListProps, AbstractList } from './AbstractList'; -/** @deprecated Use ul/li/arr.map directly instead */ +/** + * @deprecated Use ul/li/arr.map directly instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-deprecated-list--docs + */ // no point converting, this is deprecated // eslint-disable-next-line react-prefer-function-component/react-prefer-function-component export class List extends PureComponent> { diff --git a/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx b/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx index 213f39b4243..1ac038fe75f 100644 --- a/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx +++ b/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx @@ -18,6 +18,11 @@ const MAX_DURATION_MS = 4000; const DEFAULT_ANIMATION_DELAY = 300; const MAX_TRANSLATE_X = (100 / BAR_WIDTH) * 100; +/** + * The LoadingBar is used as a simple loading slider animation in the top of its container. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-loadingbar--docs + */ export function LoadingBar({ width, delay = DEFAULT_ANIMATION_DELAY, ariaLabel = 'Loading bar' }: LoadingBarProps) { const durationMs = Math.min(Math.max(Math.round(width * MILLISECONDS_PER_PIXEL), MIN_DURATION_MS), MAX_DURATION_MS); const styles = useStyles2(getStyles, delay, durationMs); diff --git a/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx b/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx index fb3ec626437..9f50ed9d072 100644 --- a/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx +++ b/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx @@ -15,6 +15,9 @@ export interface LoadingPlaceholderProps extends HTMLAttributes } /** + * Loading indicator with a text. Used to alert a user to wait for an activity to complete. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-loadingplaceholder--docs * @public */ export const LoadingPlaceholder = ({ text, className, ...rest }: LoadingPlaceholderProps) => { diff --git a/packages/grafana-ui/src/components/Menu/Menu.tsx b/packages/grafana-ui/src/components/Menu/Menu.tsx index f77a26bb0e7..a1439aa5356 100644 --- a/packages/grafana-ui/src/components/Menu/Menu.tsx +++ b/packages/grafana-ui/src/components/Menu/Menu.tsx @@ -22,6 +22,9 @@ export interface MenuProps extends React.HTMLAttributes { onKeyDown?: React.KeyboardEventHandler; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-menu--docs + */ const MenuComp = React.forwardRef( ({ header, children, ariaLabel, onOpen, onClose, onKeyDown, ...otherProps }, forwardedRef) => { const styles = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/Modal/Modal.tsx b/packages/grafana-ui/src/components/Modal/Modal.tsx index 9937fe93f9f..fc00bc9e562 100644 --- a/packages/grafana-ui/src/components/Modal/Modal.tsx +++ b/packages/grafana-ui/src/components/Modal/Modal.tsx @@ -48,6 +48,9 @@ interface WithCustomTitleProps extends BaseProps { export type Props = WithStringTitleProps | WithCustomTitleProps; +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-modal--docs + */ export function Modal(props: PropsWithChildren) { const { title, diff --git a/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx b/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx index 276520b873d..a0e17c92b1d 100644 --- a/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx +++ b/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx @@ -196,6 +196,11 @@ class UnthemedCodeEditor extends PureComponent { } } +/** + * Monaco Code editor. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-codeeditor--docs + */ export const CodeEditor = withTheme2(UnthemedCodeEditor); const getStyles = (theme: GrafanaTheme2) => { diff --git a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx index 296d79212f6..93e59296166 100644 --- a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx +++ b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx @@ -34,7 +34,11 @@ export interface Props { forceShowLeftItems?: boolean; } -/** @deprecated Use Page instead */ +/** + * @deprecated Use Page instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-deprecated-pagetoolbar--docs + */ export const PageToolbar = memo( ({ title, diff --git a/packages/grafana-ui/src/components/Pagination/Pagination.tsx b/packages/grafana-ui/src/components/Pagination/Pagination.tsx index 70c447e059c..c0c25d4186b 100644 --- a/packages/grafana-ui/src/components/Pagination/Pagination.tsx +++ b/packages/grafana-ui/src/components/Pagination/Pagination.tsx @@ -21,6 +21,11 @@ export interface Props { className?: string; } +/** + * Component used for rendering a page selector below paginated content. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-pagination--docs + */ export const Pagination = ({ currentPage, numberOfPages, diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx index 16d67ea51db..05d3ae406e5 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -116,6 +116,10 @@ interface HoverHeader { export type PanelPadding = 'none' | 'md'; /** + * Component used for rendering content wrapped in the same style as grafana panels. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-panelchrome--docs + * * @internal */ export function PanelChrome({ diff --git a/packages/grafana-ui/src/components/PanelContainer/PanelContainer.tsx b/packages/grafana-ui/src/components/PanelContainer/PanelContainer.tsx index 5279c363675..fd68335b2a7 100644 --- a/packages/grafana-ui/src/components/PanelContainer/PanelContainer.tsx +++ b/packages/grafana-ui/src/components/PanelContainer/PanelContainer.tsx @@ -8,7 +8,11 @@ import { useStyles2 } from '../../themes/ThemeContext'; type Props = DetailedHTMLProps, HTMLDivElement>; // TODO: Reimplement this with Box -/** @deprecated Use Box instead */ +/** + * @deprecated Use Box instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-deprecated-panelcontainer--docs + */ export const PanelContainer = ({ children, className, ...props }: Props) => { const styles = useStyles2(getStyles); return ( diff --git a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx index 698e61c466b..9935f2323d1 100644 --- a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx +++ b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx @@ -22,6 +22,8 @@ export interface PluginSignatureBadgeProps extends HTMLAttributes { plugins: Array>; runOnChangeDebounced: Function; @@ -238,6 +232,16 @@ export class UnThemedQueryField extends PureComponent { diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx index 128c56b1cb6..3dcc25e8875 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx @@ -72,6 +72,9 @@ export type RadialGradientMode = 'none' | 'auto'; export type RadialTextMode = 'auto' | 'value_and_name' | 'value' | 'name' | 'none'; export type RadialShape = 'circle' | 'gauge'; +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-radialgauge--docs + */ export function RadialGauge(props: RadialGaugeProps) { const { width = 256, diff --git a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx index d99958e88d1..363222fcad6 100644 --- a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx +++ b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx @@ -29,6 +29,11 @@ export interface Props { isOnCanvas?: boolean; } +/** + * This component is used on dashboards to refresh visualizations. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/pickers-refreshpicker--docs + */ export class RefreshPicker extends PureComponent { static offOption = { label: 'Off', diff --git a/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.tsx b/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.tsx index d5ec22c2440..3fb806bc162 100644 --- a/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.tsx +++ b/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.tsx @@ -9,6 +9,11 @@ export interface RenderUserContentAsHTMLProps content: string; } +/** + * Abstraction layer component for sanitizing and rendering an html content. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/utilities-renderusercontentashtml--docs + */ export function RenderUserContentAsHTML({ component, content, diff --git a/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.tsx b/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.tsx index af7907b9562..60da6f97a8d 100644 --- a/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.tsx +++ b/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.tsx @@ -17,6 +17,11 @@ interface Props extends Omit>( ( { diff --git a/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx b/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx index 4bfaac7c603..238d3adf3ea 100644 --- a/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx +++ b/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx @@ -32,6 +32,8 @@ export interface Props extends Omit, 'onRe * to the user (like datasource passwords). * * @deprecated Please use the {@link SecretInput} component with a {@link Field} instead, {@link https://developers.grafana.com/ui/latest/index.html?path=/story/forms-secretinput--basic as seen in Storybook} + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-deprecated-secretformfield--docs */ export const SecretFormField = ({ label = 'Password', diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx index b7cc7b41a72..23286c5a393 100644 --- a/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx @@ -14,6 +14,11 @@ export type Props = React.ComponentProps & { export const CONFIGURED_TEXT = 'configured'; export const RESET_BUTTON_TEXT = 'Reset'; +/** + * Used for secret/password input. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-secretinput--docs + */ export const SecretInput = ({ isConfigured, onReset, ...props }: Props) => ( {!isConfigured && } diff --git a/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx b/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx index b7196732660..a10ad157120 100644 --- a/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx +++ b/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx @@ -32,6 +32,8 @@ const getStyles = (theme: GrafanaTheme2) => { /** * Text area that does not disclose an already configured value but lets the user reset the current value and enter a new one. * Typically useful for asymmetric cryptography keys. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-secrettextarea--docs */ export const SecretTextArea = ({ isConfigured, onReset, ...props }: Props) => { const styles = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/Segment/Segment.tsx b/packages/grafana-ui/src/components/Segment/Segment.tsx index 3abec60b774..b39399965c4 100644 --- a/packages/grafana-ui/src/components/Segment/Segment.tsx +++ b/packages/grafana-ui/src/components/Segment/Segment.tsx @@ -20,6 +20,9 @@ export interface SegmentSyncProps extends SegmentProps, Omit({ options, value, diff --git a/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx b/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx index 804b1bbe92f..8a3caf6e73e 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx @@ -29,6 +29,9 @@ export interface SegmentAsyncProps extends SegmentProps, Omit({ value, onChange, diff --git a/packages/grafana-ui/src/components/Segment/SegmentInput.tsx b/packages/grafana-ui/src/components/Segment/SegmentInput.tsx index 22e9411297e..bcb46c93f04 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentInput.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentInput.tsx @@ -20,6 +20,9 @@ export interface SegmentInputProps const FONT_SIZE = 14; +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-segmentinput--docs + */ export function SegmentInput({ value: initialValue, onChange, diff --git a/packages/grafana-ui/src/components/Select/Select.tsx b/packages/grafana-ui/src/components/Select/Select.tsx index f543fa5dab5..7aaa338ae1c 100644 --- a/packages/grafana-ui/src/components/Select/Select.tsx +++ b/packages/grafana-ui/src/components/Select/Select.tsx @@ -10,11 +10,20 @@ import { VirtualizedSelectAsyncProps, } from './types'; -/** @deprecated Use Combobox component instead */ +/** + * @deprecated Use Combobox component instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function Select(props: SelectCommonProps & Rest) { return ; } +/** + * @deprecated Use Combobox component instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function MultiSelect(props: MultiSelectCommonProps & Rest) { // @ts-ignore return ; @@ -25,17 +34,29 @@ export interface AsyncSelectProps extends Omit, 'options value?: T | SelectableValue | null; } -/** @deprecated Use Combobox component instead */ +/** + * @deprecated Use Combobox component instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function AsyncSelect(props: AsyncSelectProps & Rest) { return ; } -/** @deprecated Use Combobox component instead - it's virtualised by default! */ +/** + * @deprecated Use Combobox component instead - it's virtualised by default! + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function VirtualizedSelect(props: VirtualizedSelectProps & Rest) { return ; } -/** @deprecated Use Combobox component instead - it's virtualised by default! */ +/** + * @deprecated Use Combobox component instead - it's virtualised by default! + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function AsyncVirtualizedSelect(props: VirtualizedSelectAsyncProps & Rest) { return ; } @@ -45,6 +66,11 @@ interface AsyncMultiSelectProps extends Omit, 'opti value?: Array>; } +/** + * @deprecated Use Combobox component instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function AsyncMultiSelect(props: AsyncMultiSelectProps & Rest) { // @ts-ignore return ; diff --git a/packages/grafana-ui/src/components/Slider/RangeSlider.tsx b/packages/grafana-ui/src/components/Slider/RangeSlider.tsx index 794ea5b795f..6b791fd7211 100644 --- a/packages/grafana-ui/src/components/Slider/RangeSlider.tsx +++ b/packages/grafana-ui/src/components/Slider/RangeSlider.tsx @@ -15,6 +15,8 @@ import { RangeSliderProps } from './types'; * @public * * RichHistoryQueriesTab uses this Range Component + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-rangeslider--docs */ export const RangeSlider = ({ min, diff --git a/packages/grafana-ui/src/components/Slider/Slider.tsx b/packages/grafana-ui/src/components/Slider/Slider.tsx index e8c377070bf..b41f41d0af7 100644 --- a/packages/grafana-ui/src/components/Slider/Slider.tsx +++ b/packages/grafana-ui/src/components/Slider/Slider.tsx @@ -13,6 +13,8 @@ import { SliderProps } from './types'; /** * @public + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-slider--docs */ export const Slider = ({ min, diff --git a/packages/grafana-ui/src/components/Spinner/Spinner.tsx b/packages/grafana-ui/src/components/Spinner/Spinner.tsx index 78905e29eec..af1c3b60aca 100644 --- a/packages/grafana-ui/src/components/Spinner/Spinner.tsx +++ b/packages/grafana-ui/src/components/Spinner/Spinner.tsx @@ -29,6 +29,10 @@ interface PropsWithDeprecatedSize extends Omit { /** * @public + * + * Spinner is `fa-spinner` icon animated. It is used to alert a user to wait for an activity to complete. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-spinner--docs */ export const Spinner = ({ className, diff --git a/packages/grafana-ui/src/components/Splitter/useSplitter.ts b/packages/grafana-ui/src/components/Splitter/useSplitter.ts index b5343ae80aa..18fb95a532e 100644 --- a/packages/grafana-ui/src/components/Splitter/useSplitter.ts +++ b/packages/grafana-ui/src/components/Splitter/useSplitter.ts @@ -49,6 +49,11 @@ const propsForDirection = { }, } as const; +/** + * The splitter creates two resizable panes, either horizontally or vertically. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/utilities-usesplitter--docs + */ export function useSplitter(options: UseSplitterOptions) { const { direction, diff --git a/packages/grafana-ui/src/components/Switch/Switch.tsx b/packages/grafana-ui/src/components/Switch/Switch.tsx index 0a8654b1425..d2b63a2d76a 100644 --- a/packages/grafana-ui/src/components/Switch/Switch.tsx +++ b/packages/grafana-ui/src/components/Switch/Switch.tsx @@ -14,6 +14,11 @@ export interface Props extends Omit, 'value'> { invalid?: boolean; } +/** + * Switch is a representation of an on-off state – like a light switch. So you can use Switch to toggle binary states. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-switch--docs + */ export const Switch = forwardRef( ({ value, checked, onChange, id, label, disabled, invalid = false, ...inputProps }, ref) => { if (checked) { diff --git a/packages/grafana-ui/src/components/Table/TableRT/Table.tsx b/packages/grafana-ui/src/components/Table/TableRT/Table.tsx index 4df81505431..ea909d4642f 100644 --- a/packages/grafana-ui/src/components/Table/TableRT/Table.tsx +++ b/packages/grafana-ui/src/components/Table/TableRT/Table.tsx @@ -40,6 +40,11 @@ const COLUMN_MIN_WIDTH = 150; const FOOTER_ROW_HEIGHT = 36; const NO_DATA_TEXT = 'No data'; +/** + * Used for displaying tabular data + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-table--docs + */ export const Table = memo((props: Props) => { const { ariaLabel, diff --git a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx index 57c3b9943e4..dfd967e04e3 100644 --- a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx +++ b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx @@ -102,7 +102,11 @@ export class UnThemedTableInputCSV extends PureComponent { } } -/** @deprecated */ +/** + * @deprecated + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-tableinputcsv--docs + */ export const TableInputCSV = withTheme2(UnThemedTableInputCSV); TableInputCSV.displayName = 'TableInputCSV'; diff --git a/packages/grafana-ui/src/components/Tabs/Tab.tsx b/packages/grafana-ui/src/components/Tabs/Tab.tsx index 6eb695df437..ae58e136aeb 100644 --- a/packages/grafana-ui/src/components/Tabs/Tab.tsx +++ b/packages/grafana-ui/src/components/Tabs/Tab.tsx @@ -31,6 +31,9 @@ export interface TabProps extends HTMLProps { disabled?: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-tabs--docs + */ export const Tab = React.forwardRef( ( { diff --git a/packages/grafana-ui/src/components/Tabs/TabContent.tsx b/packages/grafana-ui/src/components/Tabs/TabContent.tsx index a22f6102dba..9bceb46f3c8 100644 --- a/packages/grafana-ui/src/components/Tabs/TabContent.tsx +++ b/packages/grafana-ui/src/components/Tabs/TabContent.tsx @@ -9,6 +9,9 @@ interface Props extends HTMLAttributes { children: ReactNode; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-tabs--docs + */ export const TabContent = ({ children, className, ...restProps }: Props) => { const styles = useStyles2(getTabContentStyle); diff --git a/packages/grafana-ui/src/components/Tabs/TabsBar.tsx b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx index 8ba4405dcfa..c33734f7ea9 100644 --- a/packages/grafana-ui/src/components/Tabs/TabsBar.tsx +++ b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx @@ -13,6 +13,11 @@ export interface Props { hideBorder?: boolean; } +/** + * A composition component for rendering a TabBar with Tabs for navigation. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-tabs--docs + */ export const TabsBar = forwardRef(({ children, className, hideBorder = false }, ref) => { const styles = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/Tags/Tag.tsx b/packages/grafana-ui/src/components/Tags/Tag.tsx index e1dde995bad..1d529dfcc24 100644 --- a/packages/grafana-ui/src/components/Tags/Tag.tsx +++ b/packages/grafana-ui/src/components/Tags/Tag.tsx @@ -57,6 +57,11 @@ const TagSkeleton: SkeletonComponent = ({ rootProps }) => { return ; }; +/** + * Used for displaying metadata, for example to add more details to search results. Background and border colors are generated from the tag name. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-tag--docs + */ export const Tag = attachSkeleton(TagComponent, TagSkeleton); const getSkeletonStyles = () => ({ diff --git a/packages/grafana-ui/src/components/Tags/TagList.tsx b/packages/grafana-ui/src/components/Tags/TagList.tsx index d128c64610b..729fc920f28 100644 --- a/packages/grafana-ui/src/components/Tags/TagList.tsx +++ b/packages/grafana-ui/src/components/Tags/TagList.tsx @@ -73,6 +73,11 @@ const TagListSkeleton: SkeletonComponent = ({ rootProps }) => { ); }; +/** + * List of tags with predefined margins and positioning. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-taglist--docs + */ export const TagList = attachSkeleton(TagListComponent, TagListSkeleton); const getSkeletonStyles = (theme: GrafanaTheme2) => ({ diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx index 7bcdbb6852a..1b6e78213da 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx @@ -29,6 +29,11 @@ export interface Props { autoColors?: boolean; } +/** + * A set of an input field and a button next to it that allows the user to add new tags. The added tags are previewed next to the input and can be removed by clicking the "X" icon. You can customize the width of the input. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-tagsinput--docs + */ export const TagsInput = forwardRef( ( { diff --git a/packages/grafana-ui/src/components/Text/Text.tsx b/packages/grafana-ui/src/components/Text/Text.tsx index 55b88ee4011..56cb2b529bb 100644 --- a/packages/grafana-ui/src/components/Text/Text.tsx +++ b/packages/grafana-ui/src/components/Text/Text.tsx @@ -29,6 +29,11 @@ export interface TextProps extends Omit, 'clas children: NonNullable; } +/** + * The Text component can be used to apply typography styles in a simple way, without the need of extra css. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/foundations-text--docs + */ export const Text = React.forwardRef( ( { element = 'span', variant, weight, color, truncate, italic, textAlignment, children, tabular, ...restProps }, diff --git a/packages/grafana-ui/src/components/TextArea/TextArea.tsx b/packages/grafana-ui/src/components/TextArea/TextArea.tsx index 1c84344348f..6c80d370dc6 100644 --- a/packages/grafana-ui/src/components/TextArea/TextArea.tsx +++ b/packages/grafana-ui/src/components/TextArea/TextArea.tsx @@ -11,6 +11,11 @@ export interface Props extends Omit, 'size'> { invalid?: boolean; } +/** + * Use for multi line inputs like descriptions. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-textarea--docs + */ export const TextArea = forwardRef(({ invalid, className, ...props }, ref) => { const styles = useStyles2(getTextAreaStyle, invalid); diff --git a/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx b/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx index 991490e36aa..84f13eaf112 100644 --- a/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx +++ b/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx @@ -48,6 +48,11 @@ export interface ToggletipProps { onOpen?: () => void; } +/** + * Toggletips, similar to Tooltips, provide contextual support for users when needed. They are hidden by default, a UI trigger or text link are clicked to set them to their visible state. Toggletips, unlike tooltips, are persistent until a user takes action to dismiss them by clicking on the required “X” (close) trigger. Toggletips are capable of containing varying types of complex content including interactive components, buttons, and dropdowns. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-toggletip--docs + */ export const Toggletip = memo( ({ children, diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx index 376b1b3fc38..40b468d73fb 100644 --- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx +++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx @@ -41,6 +41,11 @@ export type ToolbarButtonProps = CommonProps & ButtonHTMLAttributes( ( { diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButtonRow.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButtonRow.tsx index 000713c57c4..54608a3acbd 100644 --- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButtonRow.tsx +++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButtonRow.tsx @@ -17,6 +17,11 @@ export interface Props extends HTMLAttributes { alignment?: 'left' | 'right'; } +/** + * A container for multiple ToolbarButtons. Provides automatic overflow behaviour when the buttons no longer fit in the container. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-toolbarbuttonrow--docs + */ export const ToolbarButtonRow = forwardRef( ({ alignment = 'left', className, children, ...rest }, ref) => { // null/undefined are valid react children so we need to filter them out to prevent unnecessary padding diff --git a/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx b/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx index b44d48ddf38..665e2d7bf6c 100644 --- a/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx @@ -34,6 +34,9 @@ export interface TooltipProps { interactive?: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-tooltip--docs + */ export const Tooltip = forwardRef( ({ children, theme, interactive, show, placement, content }, forwardedRef) => { const arrowRef = useRef(null); diff --git a/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx b/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx index dff45cb2a18..ac53b424bdb 100644 --- a/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx +++ b/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx @@ -16,6 +16,9 @@ function formatCreateLabel(input: string) { return `Custom unit: ${input}`; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/pickers-unitpicker--docs + */ export class UnitPicker extends PureComponent { onChange = (value: SelectableValue) => { this.props.onChange(value.value); diff --git a/packages/grafana-ui/src/components/UsersIndicator/UserIcon.tsx b/packages/grafana-ui/src/components/UsersIndicator/UserIcon.tsx index 3d8dd97f962..ca5ff1f665c 100644 --- a/packages/grafana-ui/src/components/UsersIndicator/UserIcon.tsx +++ b/packages/grafana-ui/src/components/UsersIndicator/UserIcon.tsx @@ -51,6 +51,11 @@ const getUserInitials = (name?: string) => { return `${first?.[0] ?? ''}${last?.[0] ?? ''}`.toUpperCase(); }; +/** + * UserIcon renders a user icon and displays the user's name or initials along with the user's active status or last viewed date. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/iconography-usericon--docs + */ export const UserIcon = ({ userView, className, diff --git a/packages/grafana-ui/src/components/UsersIndicator/UsersIndicator.tsx b/packages/grafana-ui/src/components/UsersIndicator/UsersIndicator.tsx index 44518ce25c7..66c9a0bddca 100644 --- a/packages/grafana-ui/src/components/UsersIndicator/UsersIndicator.tsx +++ b/packages/grafana-ui/src/components/UsersIndicator/UsersIndicator.tsx @@ -16,6 +16,12 @@ export interface UsersIndicatorProps { /** onClick handler for the user number indicator */ onClick?: () => void; } + +/** + * A component that displays a set of user icons indicating which users are currently active. If there are too many users to display all the icons, it will collapse the icons into a single icon with a number indicating the number of additional users. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/iconography-usersindicator--docs + */ export const UsersIndicator = ({ users, onClick, limit = 4 }: UsersIndicatorProps) => { const styles = useStyles2(getStyles); if (!users.length) { diff --git a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx index 128a038ea8d..8647140c615 100644 --- a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx +++ b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx @@ -37,6 +37,11 @@ export interface ValuePickerProps { buttonCss?: string; } +/** + * A component that looks like a button but transforms into a select when clicked. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/pickers-valuepicker--docs + */ export function ValuePicker({ 'aria-label': ariaLabel, label, diff --git a/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx b/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx index 6194b3ac6b2..11b5ed6dc00 100644 --- a/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx +++ b/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx @@ -29,6 +29,8 @@ export interface VizLayoutComponentType extends FC { /** * @beta + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-vizlayout--docs */ export const VizLayout: VizLayoutComponentType = ({ width, height, legend, children }) => { const theme = useTheme2(); diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx index f07c3abff22..0a8dc48d3a9 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx @@ -13,6 +13,8 @@ import { mapMouseEventToMode } from './utils'; /** * @public + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-vizlegend--docs */ export function VizLegend({ items, From ec0c14ac1aa2e2a1f422417905b50f6882be4aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Wed, 29 Oct 2025 14:19:24 +0100 Subject: [PATCH 07/72] log: added debug-log (#113156) * log: added debug-log * fixed unit test * fixed another unit test --- pkg/api/ds_query.go | 2 ++ pkg/api/ds_query_test.go | 3 +++ 2 files changed, 5 insertions(+) diff --git a/pkg/api/ds_query.go b/pkg/api/ds_query.go index dd33ba97817..6d27fbfc404 100644 --- a/pkg/api/ds_query.go +++ b/pkg/api/ds_query.go @@ -76,6 +76,8 @@ func (hs *HTTPServer) QueryMetricsV2(c *contextmodel.ReqContext) response.Respon var resp *backend.QueryDataResponse var err error + + hs.log.Debug("QueryMetricsV2: request received", "time_in_query", handleTimeInQuery) if handleTimeInQuery { resp, err = hs.queryDataService.QueryDataNew(c.Req.Context(), c.SignedInUser, c.SkipDSCache, reqDTO) } else { diff --git a/pkg/api/ds_query_test.go b/pkg/api/ds_query_test.go index e330f8f4438..11e12800056 100644 --- a/pkg/api/ds_query_test.go +++ b/pkg/api/ds_query_test.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/infra/db/dbtest" "github.com/grafana/grafana/pkg/infra/localcache" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" pluginClient "github.com/grafana/grafana/pkg/plugins/manager/client" @@ -86,6 +87,7 @@ func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) { server := SetupAPITestServer(t, func(hs *HTTPServer) { hs.queryDataService = qds hs.QuotaService = quotatest.New(false, nil) + hs.log = log.New("test-logger") }) t.Run("Status code is 400 when data source response has an error", func(t *testing.T) { @@ -252,6 +254,7 @@ func TestDataSourceQueryError(t *testing.T) { err := r.Add(context.Background(), p) require.NoError(t, err) ds := &fakeDatasources.FakeDataSourceService{} + hs.log = log.New("test-logger") hs.queryDataService = query.ProvideService( cfg, &fakeDatasources.FakeCacheService{}, From f533a5a6e541539851ced957bd3dd04de7d8af7d Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Wed, 29 Oct 2025 13:20:19 +0000 Subject: [PATCH 08/72] API clients: Update API clients to include all endpoints & add hooks (#113061) --- packages/grafana-api-clients/package.json | 4 + .../rtkq/advisor/v0alpha1/endpoints.gen.ts | 494 +- .../correlations/v0alpha1/endpoints.gen.ts | 10 + .../rtkq/dashboard/v0alpha1/endpoints.gen.ts | 976 ++- .../rtkq/folder/v1beta1/endpoints.gen.ts | 14 + .../rtkq/iam/v0alpha1/endpoints.gen.ts | 1711 ++++- .../src/clients/rtkq/legacy/baseAPI.ts | 9 + .../src/clients/rtkq/legacy/endpoints.gen.ts | 6820 +++++++++++++++++ .../src/clients/rtkq/legacy/index.ts | 5 + .../rtkq/playlist/v0alpha1/endpoints.gen.ts | 234 +- .../provisioning/v0alpha1/endpoints.gen.ts | 138 +- .../rtkq/shorturl/v1alpha1/endpoints.gen.ts | 14 + .../src/scripts/generate-rtk-apis.ts | 43 +- public/app/api/clients/legacy/index.ts | 6 + public/app/core/reducers/root.ts | 2 + public/app/store/configureStore.ts | 2 + 16 files changed, 10451 insertions(+), 31 deletions(-) create mode 100644 packages/grafana-api-clients/src/clients/rtkq/legacy/baseAPI.ts create mode 100644 packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts create mode 100644 packages/grafana-api-clients/src/clients/rtkq/legacy/index.ts create mode 100644 public/app/api/clients/legacy/index.ts diff --git a/packages/grafana-api-clients/package.json b/packages/grafana-api-clients/package.json index 3aa5bd4dc6c..9741c97303b 100644 --- a/packages/grafana-api-clients/package.json +++ b/packages/grafana-api-clients/package.json @@ -48,6 +48,10 @@ "import": "./src/clients/rtkq/iam/v0alpha1/index.ts", "require": "./src/clients/rtkq/iam/v0alpha1/index.ts" }, + "./rtkq/legacy": { + "import": "./src/clients/rtkq/legacy/index.ts", + "require": "./src/clients/rtkq/legacy/index.ts" + }, "./rtkq/legacy/migrate-to-cloud": { "import": "./src/clients/rtkq/migrate-to-cloud/index.ts", "require": "./src/clients/rtkq/migrate-to-cloud/index.ts" diff --git a/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts index d054b90a9f4..8cf2fec2b60 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts @@ -1,11 +1,15 @@ import { api } from './baseAPI'; -export const addTagTypes = ['Check', 'CheckType'] as const; +export const addTagTypes = ['API Discovery', 'Check', 'CheckType'] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, }) .injectEndpoints({ endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/advisor.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), listCheck: build.query({ query: (queryArg) => ({ url: `/checks`, @@ -39,6 +43,29 @@ const injectedRtkApi = api }), invalidatesTags: ['Check'], }), + deletecollectionCheck: build.mutation({ + query: (queryArg) => ({ + url: `/checks`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['Check'], + }), getCheck: build.query({ query: (queryArg) => ({ url: `/checks/${queryArg.name}`, @@ -48,6 +75,20 @@ const injectedRtkApi = api }), providesTags: ['Check'], }), + replaceCheck: build.mutation({ + query: (queryArg) => ({ + url: `/checks/${queryArg.name}`, + method: 'PUT', + body: queryArg.check, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Check'], + }), deleteCheck: build.mutation({ query: (queryArg) => ({ url: `/checks/${queryArg.name}`, @@ -78,6 +119,44 @@ const injectedRtkApi = api }), invalidatesTags: ['Check'], }), + getCheckStatus: build.query({ + query: (queryArg) => ({ + url: `/checks/${queryArg.name}/status`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Check'], + }), + replaceCheckStatus: build.mutation({ + query: (queryArg) => ({ + url: `/checks/${queryArg.name}/status`, + method: 'PUT', + body: queryArg.check, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Check'], + }), + updateCheckStatus: build.mutation({ + query: (queryArg) => ({ + url: `/checks/${queryArg.name}/status`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Check'], + }), listCheckType: build.query({ query: (queryArg) => ({ url: `/checktypes`, @@ -97,6 +176,81 @@ const injectedRtkApi = api }), providesTags: ['CheckType'], }), + createCheckType: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes`, + method: 'POST', + body: queryArg.checkType, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['CheckType'], + }), + deletecollectionCheckType: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['CheckType'], + }), + getCheckType: build.query({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['CheckType'], + }), + replaceCheckType: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}`, + method: 'PUT', + body: queryArg.checkType, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['CheckType'], + }), + deleteCheckType: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['CheckType'], + }), updateCheckType: build.mutation({ query: (queryArg) => ({ url: `/checktypes/${queryArg.name}`, @@ -112,10 +266,50 @@ const injectedRtkApi = api }), invalidatesTags: ['CheckType'], }), + getCheckTypeStatus: build.query({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}/status`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['CheckType'], + }), + replaceCheckTypeStatus: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}/status`, + method: 'PUT', + body: queryArg.checkType, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['CheckType'], + }), + updateCheckTypeStatus: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}/status`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['CheckType'], + }), }), overrideExisting: false, }); export { injectedRtkApi as generatedAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList; +export type GetApiResourcesApiArg = void; export type ListCheckApiResponse = /** status 200 OK */ CheckList; export type ListCheckApiArg = { /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ @@ -176,6 +370,57 @@ export type CreateCheckApiArg = { fieldValidation?: string; check: Check; }; +export type DeletecollectionCheckApiResponse = /** status 200 OK */ Status; +export type DeletecollectionCheckApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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". + + This 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. */ + continue?: string; + /** 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 */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; export type GetCheckApiResponse = /** status 200 OK */ Check; export type GetCheckApiArg = { /** name of the Check */ @@ -183,6 +428,20 @@ export type GetCheckApiArg = { /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ pretty?: string; }; +export type ReplaceCheckApiResponse = /** status 200 OK */ Check | /** status 201 Created */ Check; +export type ReplaceCheckApiArg = { + /** name of the Check */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + check: Check; +}; export type DeleteCheckApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; export type DeleteCheckApiArg = { /** name of the Check */ @@ -216,6 +475,43 @@ export type UpdateCheckApiArg = { force?: boolean; patch: Patch; }; +export type GetCheckStatusApiResponse = /** status 200 OK */ Check; +export type GetCheckStatusApiArg = { + /** name of the Check */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceCheckStatusApiResponse = /** status 200 OK */ Check | /** status 201 Created */ Check; +export type ReplaceCheckStatusApiArg = { + /** name of the Check */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + check: Check; +}; +export type UpdateCheckStatusApiResponse = /** status 200 OK */ Check | /** status 201 Created */ Check; +export type UpdateCheckStatusApiArg = { + /** name of the Check */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; export type ListCheckTypeApiResponse = /** status 200 OK */ CheckTypeList; export type ListCheckTypeApiArg = { /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ @@ -261,6 +557,110 @@ export type ListCheckTypeApiArg = { /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ watch?: boolean; }; +export type CreateCheckTypeApiResponse = /** status 200 OK */ + | CheckType + | /** status 201 Created */ CheckType + | /** status 202 Accepted */ CheckType; +export type CreateCheckTypeApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + checkType: CheckType; +}; +export type DeletecollectionCheckTypeApiResponse = /** status 200 OK */ Status; +export type DeletecollectionCheckTypeApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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". + + This 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. */ + continue?: string; + /** 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 */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetCheckTypeApiResponse = /** status 200 OK */ CheckType; +export type GetCheckTypeApiArg = { + /** name of the CheckType */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceCheckTypeApiResponse = /** status 200 OK */ CheckType | /** status 201 Created */ CheckType; +export type ReplaceCheckTypeApiArg = { + /** name of the CheckType */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + checkType: CheckType; +}; +export type DeleteCheckTypeApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteCheckTypeApiArg = { + /** name of the CheckType */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; export type UpdateCheckTypeApiResponse = /** status 200 OK */ CheckType | /** status 201 Created */ CheckType; export type UpdateCheckTypeApiArg = { /** name of the CheckType */ @@ -277,6 +677,75 @@ export type UpdateCheckTypeApiArg = { force?: boolean; patch: Patch; }; +export type GetCheckTypeStatusApiResponse = /** status 200 OK */ CheckType; +export type GetCheckTypeStatusApiArg = { + /** name of the CheckType */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceCheckTypeStatusApiResponse = /** status 200 OK */ CheckType | /** status 201 Created */ CheckType; +export type ReplaceCheckTypeStatusApiArg = { + /** name of the CheckType */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + checkType: CheckType; +}; +export type UpdateCheckTypeStatusApiResponse = /** status 200 OK */ CheckType | /** status 201 Created */ CheckType; +export type UpdateCheckTypeStatusApiArg = { + /** name of the CheckType */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; +export type ApiResource = { + /** categories is a list of the grouped resources this resource belongs to (e.g. 'all') */ + categories?: string[]; + /** group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale". */ + group?: string; + /** kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') */ + kind: string; + /** name is the plural name of the resource. */ + name: string; + /** namespaced indicates if a resource is namespaced or not. */ + namespaced: boolean; + /** shortNames is a list of suggested short names of the resource. */ + shortNames?: string[]; + /** singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface. */ + singularName: string; + /** The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates. */ + storageVersionHash?: string; + /** verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy) */ + verbs: string[]; + /** version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". */ + version?: string; +}; +export type ApiResourceList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** groupVersion is the group and version this APIResourceList is for. */ + groupVersion: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** resources contains the name of the resources and if they are namespaced. */ + resources: ApiResource[]; +}; export type Time = string; export type FieldsV1 = object; export type ManagedFieldsEntry = { @@ -537,3 +1006,26 @@ export type CheckTypeList = { kind?: string; metadata: ListMeta; }; +export const { + useGetApiResourcesQuery, + useListCheckQuery, + useCreateCheckMutation, + useDeletecollectionCheckMutation, + useGetCheckQuery, + useReplaceCheckMutation, + useDeleteCheckMutation, + useUpdateCheckMutation, + useGetCheckStatusQuery, + useReplaceCheckStatusMutation, + useUpdateCheckStatusMutation, + useListCheckTypeQuery, + useCreateCheckTypeMutation, + useDeletecollectionCheckTypeMutation, + useGetCheckTypeQuery, + useReplaceCheckTypeMutation, + useDeleteCheckTypeMutation, + useUpdateCheckTypeMutation, + useGetCheckTypeStatusQuery, + useReplaceCheckTypeStatusMutation, + useUpdateCheckTypeStatusMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts index ef81f37a246..9f40ec322a5 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts @@ -509,3 +509,13 @@ export type Status = { status?: string; }; export type Patch = object; +export const { + useGetApiResourcesQuery, + useListCorrelationQuery, + useCreateCorrelationMutation, + useDeletecollectionCorrelationMutation, + useGetCorrelationQuery, + useReplaceCorrelationMutation, + useDeleteCorrelationMutation, + useUpdateCorrelationMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts index 5abfbe269e8..9e4aee6b516 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts @@ -1,11 +1,240 @@ import { api } from './baseAPI'; -export const addTagTypes = ['Search'] as const; +export const addTagTypes = ['API Discovery', 'Dashboard', 'LibraryPanel', 'Search'] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, }) .injectEndpoints({ endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/dashboard.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), + listDashboard: build.query({ + query: (queryArg) => ({ + url: `/dashboards`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['Dashboard'], + }), + createDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards`, + method: 'POST', + body: queryArg.dashboard, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Dashboard'], + }), + deletecollectionDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['Dashboard'], + }), + getDashboard: build.query({ + query: (queryArg) => ({ + url: `/dashboards/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Dashboard'], + }), + replaceDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/${queryArg.name}`, + method: 'PUT', + body: queryArg.dashboard, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Dashboard'], + }), + deleteDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['Dashboard'], + }), + updateDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Dashboard'], + }), + getDashboardDto: build.query({ + query: (queryArg) => ({ url: `/dashboards/${queryArg.name}/dto` }), + providesTags: ['Dashboard'], + }), + listLibraryPanel: build.query({ + query: (queryArg) => ({ + url: `/librarypanels`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['LibraryPanel'], + }), + createLibraryPanel: build.mutation({ + query: (queryArg) => ({ + url: `/librarypanels`, + method: 'POST', + body: queryArg.libraryPanel, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['LibraryPanel'], + }), + deletecollectionLibraryPanel: build.mutation< + DeletecollectionLibraryPanelApiResponse, + DeletecollectionLibraryPanelApiArg + >({ + query: (queryArg) => ({ + url: `/librarypanels`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['LibraryPanel'], + }), + getLibraryPanel: build.query({ + query: (queryArg) => ({ + url: `/librarypanels/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['LibraryPanel'], + }), + replaceLibraryPanel: build.mutation({ + query: (queryArg) => ({ + url: `/librarypanels/${queryArg.name}`, + method: 'PUT', + body: queryArg.libraryPanel, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['LibraryPanel'], + }), + deleteLibraryPanel: build.mutation({ + query: (queryArg) => ({ + url: `/librarypanels/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['LibraryPanel'], + }), + updateLibraryPanel: build.mutation({ + query: (queryArg) => ({ + url: `/librarypanels/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['LibraryPanel'], + }), getSearch: build.query({ query: (queryArg) => ({ url: `/search`, @@ -22,10 +251,351 @@ const injectedRtkApi = api }), providesTags: ['Search'], }), + getSearchSortable: build.query({ + query: () => ({ url: `/search/sortable` }), + providesTags: ['Search'], + }), }), overrideExisting: false, }); export { injectedRtkApi as generatedAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList; +export type GetApiResourcesApiArg = void; +export type ListDashboardApiResponse = /** status 200 OK */ DashboardList; +export type ListDashboardApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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. */ + allowWatchBookmarks?: boolean; + /** 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". + + This 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. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateDashboardApiResponse = /** status 200 OK */ + | Dashboard + | /** status 201 Created */ Dashboard + | /** status 202 Accepted */ Dashboard; +export type CreateDashboardApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + dashboard: Dashboard; +}; +export type DeletecollectionDashboardApiResponse = /** status 200 OK */ Status; +export type DeletecollectionDashboardApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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". + + This 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. */ + continue?: string; + /** 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 */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetDashboardApiResponse = /** status 200 OK */ Dashboard; +export type GetDashboardApiArg = { + /** name of the Dashboard */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceDashboardApiResponse = /** status 200 OK */ Dashboard | /** status 201 Created */ Dashboard; +export type ReplaceDashboardApiArg = { + /** name of the Dashboard */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + dashboard: Dashboard; +}; +export type DeleteDashboardApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteDashboardApiArg = { + /** name of the Dashboard */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateDashboardApiResponse = /** status 200 OK */ Dashboard | /** status 201 Created */ Dashboard; +export type UpdateDashboardApiArg = { + /** name of the Dashboard */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; +export type GetDashboardDtoApiResponse = /** status 200 OK */ DashboardWithAccessInfo; +export type GetDashboardDtoApiArg = { + /** name of the DashboardWithAccessInfo */ + name: string; +}; +export type ListLibraryPanelApiResponse = /** status 200 OK */ LibraryPanelList; +export type ListLibraryPanelApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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. */ + allowWatchBookmarks?: boolean; + /** 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". + + This 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. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateLibraryPanelApiResponse = /** status 200 OK */ + | LibraryPanel + | /** status 201 Created */ LibraryPanel + | /** status 202 Accepted */ LibraryPanel; +export type CreateLibraryPanelApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + libraryPanel: LibraryPanel; +}; +export type DeletecollectionLibraryPanelApiResponse = /** status 200 OK */ Status; +export type DeletecollectionLibraryPanelApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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". + + This 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. */ + continue?: string; + /** 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 */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetLibraryPanelApiResponse = /** status 200 OK */ LibraryPanel; +export type GetLibraryPanelApiArg = { + /** name of the LibraryPanel */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceLibraryPanelApiResponse = /** status 200 OK */ LibraryPanel | /** status 201 Created */ LibraryPanel; +export type ReplaceLibraryPanelApiArg = { + /** name of the LibraryPanel */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + libraryPanel: LibraryPanel; +}; +export type DeleteLibraryPanelApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteLibraryPanelApiArg = { + /** name of the LibraryPanel */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateLibraryPanelApiResponse = /** status 200 OK */ LibraryPanel | /** status 201 Created */ LibraryPanel; +export type UpdateLibraryPanelApiArg = { + /** name of the LibraryPanel */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; export type GetSearchApiResponse = /** status 200 undefined */ SearchResults; export type GetSearchApiArg = { /** user query string */ @@ -45,6 +615,390 @@ export type GetSearchApiArg = { /** add debugging info that may help explain why the result matched */ explain?: boolean; }; +export type GetSearchSortableApiResponse = /** status 200 undefined */ { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Sortable fields (depends on backend support) */ + fields: any[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; +}; +export type GetSearchSortableApiArg = void; +export type ApiResource = { + /** categories is a list of the grouped resources this resource belongs to (e.g. 'all') */ + categories?: string[]; + /** group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale". */ + group?: string; + /** kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') */ + kind: string; + /** name is the plural name of the resource. */ + name: string; + /** namespaced indicates if a resource is namespaced or not. */ + namespaced: boolean; + /** shortNames is a list of suggested short names of the resource. */ + shortNames?: string[]; + /** singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface. */ + singularName: string; + /** The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates. */ + storageVersionHash?: string; + /** verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy) */ + verbs: string[]; + /** version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". */ + version?: string; +}; +export type ApiResourceList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** groupVersion is the group and version this APIResourceList is for. */ + groupVersion: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** resources contains the name of the resources and if they are namespaced. */ + resources: ApiResource[]; +}; +export type Time = string; +export type FieldsV1 = object; +export type ManagedFieldsEntry = { + /** APIVersion defines the version of this resource that this field set applies to. The format is "group/version" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted. */ + apiVersion?: string; + /** FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: "FieldsV1" */ + fieldsType?: string; + /** FieldsV1 holds the first JSON version format as described in the "FieldsV1" type. */ + fieldsV1?: FieldsV1; + /** Manager is an identifier of the workflow managing these fields. */ + manager?: string; + /** Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'. */ + operation?: string; + /** Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource. */ + subresource?: string; + /** Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over. */ + time?: Time; +}; +export type OwnerReference = { + /** API version of the referent. */ + apiVersion: string; + /** If true, AND if the owner has the "foregroundDeletion" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned. */ + blockOwnerDeletion?: boolean; + /** If true, this reference points to the managing controller. */ + controller?: boolean; + /** Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind: string; + /** Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names */ + name: string; + /** UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid: string; +}; +export type ObjectMeta = { + /** Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations */ + annotations?: { + [key: string]: string; + }; + /** CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + + Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + creationTimestamp?: Time; + /** Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only. */ + deletionGracePeriodSeconds?: number; + /** DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested. + + Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + deletionTimestamp?: Time; + /** Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list. */ + finalizers?: string[]; + /** GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. + + If this field is specified and the generated name exists, the server will return a 409. + + Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency */ + generateName?: string; + /** A sequence number representing a specific generation of the desired state. Populated by the system. Read-only. */ + generation?: number; + /** Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels */ + labels?: { + [key: string]: string; + }; + /** ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like "ci-cd". The set of fields is always in the version that the workflow used when modifying the object. */ + managedFields?: ManagedFieldsEntry[]; + /** Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names */ + name?: string; + /** Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. + + Must be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces */ + namespace?: string; + /** List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller. */ + ownerReferences?: OwnerReference[]; + /** An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. + + Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency */ + resourceVersion?: string; + /** Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. */ + selfLink?: string; + /** UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. + + Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid?: string; +}; +export type Unstructured = { + [key: string]: any; +}; +export type DashboardConversionStatus = { + /** The error message from the conversion. Empty if the conversion has not failed. */ + error?: string; + /** Whether from another version has failed. If true, means that the dashboard is not valid, and the caller should instead fetch the stored version. */ + failed: boolean; + /** The original value map[string]any */ + source?: object; + /** The version which was stored when the dashboard was created / updated. Fetching this version should always succeed. */ + storedVersion?: string; +}; +export type DashboardStatus = { + /** Optional conversion status. */ + conversion?: DashboardConversionStatus; +}; +export type Dashboard = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: ObjectMeta; + /** Spec is the spec of the Dashboard */ + spec: Unstructured; + status: DashboardStatus; +}; +export type ListMeta = { + /** continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message. */ + continue?: string; + /** remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact. */ + remainingItemCount?: number; + /** String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency */ + resourceVersion?: string; + /** Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. */ + selfLink?: string; +}; +export type DashboardList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: Dashboard[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: ListMeta; +}; +export type StatusCause = { + /** The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. + + Examples: + "name" - the field "name" on the current resource + "items[0].name" - the field "name" on the first array entry in "items" */ + field?: string; + /** A human-readable description of the cause of the error. This field may be presented as-is to a reader. */ + message?: string; + /** A machine-readable description of the cause of the error. If this value is empty there is no information available. */ + reason?: string; +}; +export type StatusDetails = { + /** The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes. */ + causes?: StatusCause[]; + /** The group attribute of the resource associated with the status StatusReason. */ + group?: string; + /** The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described). */ + name?: string; + /** If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action. */ + retryAfterSeconds?: number; + /** UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid?: string; +}; +export type Status = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Suggested HTTP return code for this status, 0 if not set. */ + code?: number; + /** Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type. */ + details?: StatusDetails; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** A human-readable description of the status of this operation. */ + message?: string; + /** Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + metadata?: ListMeta; + /** A machine-readable description of why this operation is in the "Failure" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it. */ + reason?: string; + /** Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status */ + status?: string; +}; +export type Patch = object; +export type AnnotationActions = { + canAdd: boolean; + canDelete: boolean; + canEdit: boolean; +}; +export type AnnotationPermission = { + dashboard: AnnotationActions; + organization: AnnotationActions; +}; +export type DashboardAccess = { + annotationsPermissions: AnnotationPermission; + canAdmin: boolean; + canDelete: boolean; + canEdit: boolean; + /** The permissions part */ + canSave: boolean; + canStar: boolean; + /** Metadata fields */ + slug?: string; + url?: string; +}; +export type DashboardWithAccessInfo = { + access: DashboardAccess; + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: ObjectMeta; + /** Spec is the spec of the Dashboard */ + spec: Unstructured; + status: DashboardStatus; +}; +export type DataSourceRef = { + /** The apiserver version */ + apiVersion?: string; + /** The datasource plugin type */ + type: string; + /** Datasource UID (NOTE: name in k8s) */ + uid?: string; +}; +export type GridPos = { + h: number; + w: number; + x: number; + y: number; +}; +export type DataQuery = { + /** The datasource */ + datasource?: { + /** The apiserver version */ + apiVersion?: string; + /** The datasource plugin type */ + type: string; + /** Datasource UID (NOTE: name in k8s) */ + uid?: string; + }; + /** true if query is disabled (ie should not be returned to the dashboard) + NOTE: this does not always imply that the query should not be executed since + the results from a hidden query may be used as the input to other queries (SSE etc) */ + hide?: boolean; + /** Interval is the suggested duration between time points in a time series query. + NOTE: the values for intervalMs is not saved in the query model. It is typically calculated + from the interval required to fill a pixels in the visualization */ + intervalMs?: number; + /** MaxDataPoints is the maximum number of data points that should be returned from a time series query. + NOTE: the values for maxDataPoints is not saved in the query model. It is typically calculated + from the number of pixels visible in a visualization */ + maxDataPoints?: number; + /** QueryType is an optional identifier for the type of query. + It can be used to distinguish different types of queries. */ + queryType?: string; + /** RefID is the unique identifier of the query, set by the frontend call. */ + refId?: string; + /** Optionally define expected query result behavior */ + resultAssertions?: { + /** Maximum frame count */ + maxFrames?: number; + /** Type asserts that the frame matches a known type structure. + + + Possible enum values: + - `""` + - `"timeseries-wide"` + - `"timeseries-long"` + - `"timeseries-many"` + - `"timeseries-multi"` + - `"directory-listing"` + - `"table"` + - `"numeric-wide"` + - `"numeric-multi"` + - `"numeric-long"` + - `"log-lines"` */ + type?: + | '' + | 'timeseries-wide' + | 'timeseries-long' + | 'timeseries-many' + | 'timeseries-multi' + | 'directory-listing' + | 'table' + | 'numeric-wide' + | 'numeric-multi' + | 'numeric-long' + | 'log-lines'; + /** TypeVersion is the version of the Type property. Versions greater than 0.0 correspond to the dataplane + contract documentation https://grafana.github.io/dataplane/contract/. */ + typeVersion: number[]; + }; + /** TimeRange represents the query range + NOTE: unlike generic /ds/query, we can now send explicit time values in each query + NOTE: the values for timeRange are not saved in a dashboard, they are constructed on the fly */ + timeRange?: { + /** From is the start time of the query. */ + from: string; + /** To is the end time of the query. */ + to: string; + }; + [key: string]: any; +}; +export type LibraryPanelSpec = { + /** The default datasource type */ + datasource?: DataSourceRef; + /** Library panel description */ + description?: string; + /** The fieldConfig schema depends on the panel type */ + fieldConfig: Unstructured; + /** The grid position */ + gridPos?: GridPos; + /** The links for the panel */ + links?: Unstructured[]; + /** The options schema depends on the panel type */ + options: Unstructured; + /** The title of the panel when displayed in the dashboard */ + panelTitle?: string; + /** The panel type */ + pluginVersion?: string; + /** The datasource queries */ + targets?: DataQuery[]; + /** The title of the library panel */ + title?: string; + /** Whether the panel is transparent */ + transparent?: boolean; + /** The panel type */ + type: string; +}; +export type LibraryPanelStatus = { + /** The properties previously stored in SQL that are not included in this model */ + missing?: Unstructured; + /** Translation warnings (mostly things that were in SQL columns but not found in the saved body) */ + warnings?: string[]; +}; +export type LibraryPanel = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + metadata?: ObjectMeta; + /** Panel properties */ + spec: LibraryPanelSpec; + /** Status will show errors */ + status?: LibraryPanelStatus; +}; +export type LibraryPanelList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: LibraryPanel[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata?: ListMeta; +}; export type TermFacet = { count?: number; term?: string; @@ -105,3 +1059,23 @@ export type SearchResults = { /** The number of matching results */ totalHits: number; }; +export const { + useGetApiResourcesQuery, + useListDashboardQuery, + useCreateDashboardMutation, + useDeletecollectionDashboardMutation, + useGetDashboardQuery, + useReplaceDashboardMutation, + useDeleteDashboardMutation, + useUpdateDashboardMutation, + useGetDashboardDtoQuery, + useListLibraryPanelQuery, + useCreateLibraryPanelMutation, + useDeletecollectionLibraryPanelMutation, + useGetLibraryPanelQuery, + useReplaceLibraryPanelMutation, + useDeleteLibraryPanelMutation, + useUpdateLibraryPanelMutation, + useGetSearchQuery, + useGetSearchSortableQuery, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts index ae8bba67c4b..b618f8b335a 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts @@ -560,3 +560,17 @@ export type FolderInfoList = { kind?: string; metadata?: ListMeta; }; +export const { + useGetApiResourcesQuery, + useListFolderQuery, + useCreateFolderMutation, + useDeletecollectionFolderMutation, + useGetFolderQuery, + useReplaceFolderMutation, + useDeleteFolderMutation, + useUpdateFolderMutation, + useGetFolderAccessQuery, + useGetFolderChildrenQuery, + useGetFolderCountsQuery, + useGetFolderParentsQuery, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts index d59b1d4cd52..bd1585fc928 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts @@ -1,11 +1,23 @@ import { api } from './baseAPI'; -export const addTagTypes = ['Display'] as const; +export const addTagTypes = [ + 'API Discovery', + 'Display', + 'ServiceAccount', + 'SSOSetting', + 'TeamBinding', + 'Team', + 'User', +] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, }) .injectEndpoints({ endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/iam.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), getDisplayMapping: build.query({ query: (queryArg) => ({ url: `/display`, @@ -15,15 +27,1353 @@ const injectedRtkApi = api }), providesTags: ['Display'], }), + listServiceAccount: build.query({ + query: (queryArg) => ({ + url: `/serviceaccounts`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['ServiceAccount'], + }), + createServiceAccount: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts`, + method: 'POST', + body: queryArg.serviceAccount, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['ServiceAccount'], + }), + deletecollectionServiceAccount: build.mutation< + DeletecollectionServiceAccountApiResponse, + DeletecollectionServiceAccountApiArg + >({ + query: (queryArg) => ({ + url: `/serviceaccounts`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['ServiceAccount'], + }), + getServiceAccount: build.query({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['ServiceAccount'], + }), + replaceServiceAccount: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.name}`, + method: 'PUT', + body: queryArg.serviceAccount, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['ServiceAccount'], + }), + deleteServiceAccount: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['ServiceAccount'], + }), + updateServiceAccount: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['ServiceAccount'], + }), + getServiceAccountTokens: build.query({ + query: (queryArg) => ({ url: `/serviceaccounts/${queryArg.name}/tokens` }), + providesTags: ['ServiceAccount'], + }), + listSsoSetting: build.query({ + query: (queryArg) => ({ + url: `/ssosettings`, + params: { + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + pretty: queryArg.pretty, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['SSOSetting'], + }), + getSsoSetting: build.query({ + query: (queryArg) => ({ + url: `/ssosettings/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['SSOSetting'], + }), + replaceSsoSetting: build.mutation({ + query: (queryArg) => ({ + url: `/ssosettings/${queryArg.name}`, + method: 'PUT', + body: queryArg.ssoSetting, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['SSOSetting'], + }), + deleteSsoSetting: build.mutation({ + query: (queryArg) => ({ + url: `/ssosettings/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['SSOSetting'], + }), + updateSsoSetting: build.mutation({ + query: (queryArg) => ({ + url: `/ssosettings/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['SSOSetting'], + }), + listTeamBinding: build.query({ + query: (queryArg) => ({ + url: `/teambindings`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['TeamBinding'], + }), + createTeamBinding: build.mutation({ + query: (queryArg) => ({ + url: `/teambindings`, + method: 'POST', + body: queryArg.teamBinding, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TeamBinding'], + }), + deletecollectionTeamBinding: build.mutation< + DeletecollectionTeamBindingApiResponse, + DeletecollectionTeamBindingApiArg + >({ + query: (queryArg) => ({ + url: `/teambindings`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['TeamBinding'], + }), + getTeamBinding: build.query({ + query: (queryArg) => ({ + url: `/teambindings/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['TeamBinding'], + }), + replaceTeamBinding: build.mutation({ + query: (queryArg) => ({ + url: `/teambindings/${queryArg.name}`, + method: 'PUT', + body: queryArg.teamBinding, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TeamBinding'], + }), + deleteTeamBinding: build.mutation({ + query: (queryArg) => ({ + url: `/teambindings/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['TeamBinding'], + }), + updateTeamBinding: build.mutation({ + query: (queryArg) => ({ + url: `/teambindings/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['TeamBinding'], + }), + listTeam: build.query({ + query: (queryArg) => ({ + url: `/teams`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['Team'], + }), + createTeam: build.mutation({ + query: (queryArg) => ({ + url: `/teams`, + method: 'POST', + body: queryArg.team, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Team'], + }), + deletecollectionTeam: build.mutation({ + query: (queryArg) => ({ + url: `/teams`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['Team'], + }), + getTeam: build.query({ + query: (queryArg) => ({ + url: `/teams/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Team'], + }), + replaceTeam: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.name}`, + method: 'PUT', + body: queryArg.team, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Team'], + }), + deleteTeam: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['Team'], + }), + updateTeam: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Team'], + }), + getTeamMembers: build.query({ + query: (queryArg) => ({ url: `/teams/${queryArg.name}/members` }), + providesTags: ['Team'], + }), + listUser: build.query({ + query: (queryArg) => ({ + url: `/users`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['User'], + }), + createUser: build.mutation({ + query: (queryArg) => ({ + url: `/users`, + method: 'POST', + body: queryArg.user, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['User'], + }), + deletecollectionUser: build.mutation({ + query: (queryArg) => ({ + url: `/users`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['User'], + }), + getUser: build.query({ + query: (queryArg) => ({ + url: `/users/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['User'], + }), + replaceUser: build.mutation({ + query: (queryArg) => ({ + url: `/users/${queryArg.name}`, + method: 'PUT', + body: queryArg.user, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['User'], + }), + deleteUser: build.mutation({ + query: (queryArg) => ({ + url: `/users/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['User'], + }), + updateUser: build.mutation({ + query: (queryArg) => ({ + url: `/users/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['User'], + }), + getUserTeams: build.query({ + query: (queryArg) => ({ url: `/users/${queryArg.name}/teams` }), + providesTags: ['User'], + }), }), overrideExisting: false, }); export { injectedRtkApi as generatedAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList; +export type GetApiResourcesApiArg = void; export type GetDisplayMappingApiResponse = /** status 200 undefined */ DisplayList; export type GetDisplayMappingApiArg = { /** Display keys */ key: string[]; }; +export type ListServiceAccountApiResponse = /** status 200 OK */ ServiceAccountList; +export type ListServiceAccountApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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. */ + allowWatchBookmarks?: boolean; + /** 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". + + This 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. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateServiceAccountApiResponse = /** status 200 OK */ + | ServiceAccount + | /** status 201 Created */ ServiceAccount + | /** status 202 Accepted */ ServiceAccount; +export type CreateServiceAccountApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + serviceAccount: ServiceAccount; +}; +export type DeletecollectionServiceAccountApiResponse = /** status 200 OK */ Status; +export type DeletecollectionServiceAccountApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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". + + This 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. */ + continue?: string; + /** 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 */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetServiceAccountApiResponse = /** status 200 OK */ ServiceAccount; +export type GetServiceAccountApiArg = { + /** name of the ServiceAccount */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceServiceAccountApiResponse = /** status 200 OK */ + | ServiceAccount + | /** status 201 Created */ ServiceAccount; +export type ReplaceServiceAccountApiArg = { + /** name of the ServiceAccount */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + serviceAccount: ServiceAccount; +}; +export type DeleteServiceAccountApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteServiceAccountApiArg = { + /** name of the ServiceAccount */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateServiceAccountApiResponse = /** status 200 OK */ + | ServiceAccount + | /** status 201 Created */ ServiceAccount; +export type UpdateServiceAccountApiArg = { + /** name of the ServiceAccount */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; +export type GetServiceAccountTokensApiResponse = /** status 200 OK */ ServiceAccountTokenList; +export type GetServiceAccountTokensApiArg = { + /** name of the ServiceAccountTokenList */ + name: string; +}; +export type ListSsoSettingApiResponse = /** status 200 OK */ SsoSettingList; +export type ListSsoSettingApiArg = { + /** 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. */ + allowWatchBookmarks?: boolean; + /** 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". + + This 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. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type GetSsoSettingApiResponse = /** status 200 OK */ SsoSetting; +export type GetSsoSettingApiArg = { + /** name of the SSOSetting */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceSsoSettingApiResponse = /** status 200 OK */ SsoSetting | /** status 201 Created */ SsoSetting; +export type ReplaceSsoSettingApiArg = { + /** name of the SSOSetting */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + ssoSetting: SsoSetting; +}; +export type DeleteSsoSettingApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteSsoSettingApiArg = { + /** name of the SSOSetting */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateSsoSettingApiResponse = /** status 200 OK */ SsoSetting | /** status 201 Created */ SsoSetting; +export type UpdateSsoSettingApiArg = { + /** name of the SSOSetting */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; +export type ListTeamBindingApiResponse = /** status 200 OK */ TeamBindingList; +export type ListTeamBindingApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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. */ + allowWatchBookmarks?: boolean; + /** 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". + + This 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. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateTeamBindingApiResponse = /** status 200 OK */ + | TeamBinding + | /** status 201 Created */ TeamBinding + | /** status 202 Accepted */ TeamBinding; +export type CreateTeamBindingApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + teamBinding: TeamBinding; +}; +export type DeletecollectionTeamBindingApiResponse = /** status 200 OK */ Status; +export type DeletecollectionTeamBindingApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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". + + This 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. */ + continue?: string; + /** 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 */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetTeamBindingApiResponse = /** status 200 OK */ TeamBinding; +export type GetTeamBindingApiArg = { + /** name of the TeamBinding */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceTeamBindingApiResponse = /** status 200 OK */ TeamBinding | /** status 201 Created */ TeamBinding; +export type ReplaceTeamBindingApiArg = { + /** name of the TeamBinding */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + teamBinding: TeamBinding; +}; +export type DeleteTeamBindingApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteTeamBindingApiArg = { + /** name of the TeamBinding */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateTeamBindingApiResponse = /** status 200 OK */ TeamBinding | /** status 201 Created */ TeamBinding; +export type UpdateTeamBindingApiArg = { + /** name of the TeamBinding */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; +export type ListTeamApiResponse = /** status 200 OK */ TeamList; +export type ListTeamApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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. */ + allowWatchBookmarks?: boolean; + /** 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". + + This 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. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateTeamApiResponse = /** status 200 OK */ + | Team + | /** status 201 Created */ Team + | /** status 202 Accepted */ Team; +export type CreateTeamApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + team: Team; +}; +export type DeletecollectionTeamApiResponse = /** status 200 OK */ Status; +export type DeletecollectionTeamApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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". + + This 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. */ + continue?: string; + /** 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 */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetTeamApiResponse = /** status 200 OK */ Team; +export type GetTeamApiArg = { + /** name of the Team */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceTeamApiResponse = /** status 200 OK */ Team | /** status 201 Created */ Team; +export type ReplaceTeamApiArg = { + /** name of the Team */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + team: Team; +}; +export type DeleteTeamApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteTeamApiArg = { + /** name of the Team */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateTeamApiResponse = /** status 200 OK */ Team | /** status 201 Created */ Team; +export type UpdateTeamApiArg = { + /** name of the Team */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; +export type GetTeamMembersApiResponse = /** status 200 OK */ TeamMemberList; +export type GetTeamMembersApiArg = { + /** name of the TeamMemberList */ + name: string; +}; +export type ListUserApiResponse = /** status 200 OK */ UserList; +export type ListUserApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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. */ + allowWatchBookmarks?: boolean; + /** 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". + + This 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. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateUserApiResponse = /** status 200 OK */ + | User + | /** status 201 Created */ User + | /** status 202 Accepted */ User; +export type CreateUserApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + user: User; +}; +export type DeletecollectionUserApiResponse = /** status 200 OK */ Status; +export type DeletecollectionUserApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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". + + This 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. */ + continue?: string; + /** 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 */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetUserApiResponse = /** status 200 OK */ User; +export type GetUserApiArg = { + /** name of the User */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceUserApiResponse = /** status 200 OK */ User | /** status 201 Created */ User; +export type ReplaceUserApiArg = { + /** name of the User */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + user: User; +}; +export type DeleteUserApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteUserApiArg = { + /** name of the User */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateUserApiResponse = /** status 200 OK */ User | /** status 201 Created */ User; +export type UpdateUserApiArg = { + /** name of the User */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; +export type GetUserTeamsApiResponse = /** status 200 OK */ UserTeamList; +export type GetUserTeamsApiArg = { + /** name of the UserTeamList */ + name: string; +}; +export type ApiResource = { + /** categories is a list of the grouped resources this resource belongs to (e.g. 'all') */ + categories?: string[]; + /** group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale". */ + group?: string; + /** kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') */ + kind: string; + /** name is the plural name of the resource. */ + name: string; + /** namespaced indicates if a resource is namespaced or not. */ + namespaced: boolean; + /** shortNames is a list of suggested short names of the resource. */ + shortNames?: string[]; + /** singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface. */ + singularName: string; + /** The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates. */ + storageVersionHash?: string; + /** verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy) */ + verbs: string[]; + /** version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". */ + version?: string; +}; +export type ApiResourceList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** groupVersion is the group and version this APIResourceList is for. */ + groupVersion: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** resources contains the name of the resources and if they are namespaced. */ + resources: ApiResource[]; +}; export type IdentityRef = { /** Name is the unique identifier for identity, guaranteed to be a unique value for the type within a namespace. */ name: string; @@ -62,3 +1412,362 @@ export type DisplayList = { kind?: string; metadata?: ListMeta; }; +export type Time = string; +export type FieldsV1 = object; +export type ManagedFieldsEntry = { + /** APIVersion defines the version of this resource that this field set applies to. The format is "group/version" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted. */ + apiVersion?: string; + /** FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: "FieldsV1" */ + fieldsType?: string; + /** FieldsV1 holds the first JSON version format as described in the "FieldsV1" type. */ + fieldsV1?: FieldsV1; + /** Manager is an identifier of the workflow managing these fields. */ + manager?: string; + /** Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'. */ + operation?: string; + /** Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource. */ + subresource?: string; + /** Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over. */ + time?: Time; +}; +export type OwnerReference = { + /** API version of the referent. */ + apiVersion: string; + /** If true, AND if the owner has the "foregroundDeletion" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned. */ + blockOwnerDeletion?: boolean; + /** If true, this reference points to the managing controller. */ + controller?: boolean; + /** Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind: string; + /** Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names */ + name: string; + /** UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid: string; +}; +export type ObjectMeta = { + /** Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations */ + annotations?: { + [key: string]: string; + }; + /** CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + + Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + creationTimestamp?: Time; + /** Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only. */ + deletionGracePeriodSeconds?: number; + /** DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested. + + Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + deletionTimestamp?: Time; + /** Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list. */ + finalizers?: string[]; + /** GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. + + If this field is specified and the generated name exists, the server will return a 409. + + Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency */ + generateName?: string; + /** A sequence number representing a specific generation of the desired state. Populated by the system. Read-only. */ + generation?: number; + /** Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels */ + labels?: { + [key: string]: string; + }; + /** ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like "ci-cd". The set of fields is always in the version that the workflow used when modifying the object. */ + managedFields?: ManagedFieldsEntry[]; + /** Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names */ + name?: string; + /** Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. + + Must be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces */ + namespace?: string; + /** List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller. */ + ownerReferences?: OwnerReference[]; + /** An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. + + Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency */ + resourceVersion?: string; + /** Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. */ + selfLink?: string; + /** UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. + + Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid?: string; +}; +export type ServiceAccountSpec = { + disabled: boolean; + plugin: string; + role: string; + title: string; +}; +export type ServiceAccount = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: ObjectMeta; + /** Spec is the spec of the ServiceAccount */ + spec: ServiceAccountSpec; +}; +export type ServiceAccountList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: ServiceAccount[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: ListMeta; +}; +export type StatusCause = { + /** The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. + + Examples: + "name" - the field "name" on the current resource + "items[0].name" - the field "name" on the first array entry in "items" */ + field?: string; + /** A human-readable description of the cause of the error. This field may be presented as-is to a reader. */ + message?: string; + /** A machine-readable description of the cause of the error. If this value is empty there is no information available. */ + reason?: string; +}; +export type StatusDetails = { + /** The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes. */ + causes?: StatusCause[]; + /** The group attribute of the resource associated with the status StatusReason. */ + group?: string; + /** The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described). */ + name?: string; + /** If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action. */ + retryAfterSeconds?: number; + /** UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid?: string; +}; +export type Status = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Suggested HTTP return code for this status, 0 if not set. */ + code?: number; + /** Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type. */ + details?: StatusDetails; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** A human-readable description of the status of this operation. */ + message?: string; + /** Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + metadata?: ListMeta; + /** A machine-readable description of why this operation is in the "Failure" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it. */ + reason?: string; + /** Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status */ + status?: string; +}; +export type Patch = object; +export type ServiceAccountToken = { + created: Time; + expires?: Time; + lastUsed?: Time; + name?: string; + revoked?: boolean; +}; +export type ServiceAccountTokenList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: ServiceAccountToken[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata?: ListMeta; +}; +export type Unstructured = { + [key: string]: any; +}; +export type SsoSettingSpec = { + settings: Unstructured; + /** Possible enum values: + - `"db"` + - `"system"` system is from config file, env or argument */ + source: 'db' | 'system'; +}; +export type SsoSetting = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + metadata?: ObjectMeta; + spec?: SsoSettingSpec; +}; +export type SsoSettingList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: SsoSetting[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata?: ListMeta; +}; +export type TeamBindingspecSubject = { + /** uid of the identity */ + name: string; +}; +export type TeamBindingTeamRef = { + /** Name is the unique identifier for a team. */ + name: string; +}; +export type TeamBindingSpec = { + external: boolean; + /** permission of the identity in the team */ + permission: string; + subject: TeamBindingspecSubject; + teamRef: TeamBindingTeamRef; +}; +export type TeamBinding = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: ObjectMeta; + /** Spec is the spec of the TeamBinding */ + spec: TeamBindingSpec; +}; +export type TeamBindingList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: TeamBinding[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: ListMeta; +}; +export type TeamSpec = { + email: string; + externalUID: string; + provisioned: boolean; + title: string; +}; +export type Team = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: ObjectMeta; + /** Spec is the spec of the Team */ + spec: TeamSpec; +}; +export type TeamList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: Team[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: ListMeta; +}; +export type TeamMember = { + /** AvatarURL is the url where we can get the avatar for identity */ + avatarURL?: string; + /** Display name for identity. */ + displayName: string; + /** External is set if member ship was synced from external IDP. */ + external?: boolean; + identity: IdentityRef; + /** InternalID is the legacy numeric id for identity, Deprecated: use the identityRef where possible */ + internalId?: number; + /** Permission member has in team. + + Possible enum values: + - `"admin"` + - `"member"` */ + permission?: 'admin' | 'member'; +}; +export type TeamMemberList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: TeamMember[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata?: ListMeta; +}; +export type UserSpec = { + disabled: boolean; + email: string; + emailVerified: boolean; + grafanaAdmin: boolean; + login: string; + provisioned: boolean; + role: string; + title: string; +}; +export type User = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: ObjectMeta; + /** Spec is the spec of the User */ + spec: UserSpec; +}; +export type UserList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: User[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: ListMeta; +}; +export type TeamRef = { + /** Name is the unique identifier for a team. */ + name?: string; +}; +export type UserTeam = { + /** Possible enum values: + - `"admin"` + - `"member"` */ + permission?: 'admin' | 'member'; + teamRef?: TeamRef; + title?: string; +}; +export type UserTeamList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: UserTeam[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata?: ListMeta; +}; +export const { + useGetApiResourcesQuery, + useGetDisplayMappingQuery, + useListServiceAccountQuery, + useCreateServiceAccountMutation, + useDeletecollectionServiceAccountMutation, + useGetServiceAccountQuery, + useReplaceServiceAccountMutation, + useDeleteServiceAccountMutation, + useUpdateServiceAccountMutation, + useGetServiceAccountTokensQuery, + useListSsoSettingQuery, + useGetSsoSettingQuery, + useReplaceSsoSettingMutation, + useDeleteSsoSettingMutation, + useUpdateSsoSettingMutation, + useListTeamBindingQuery, + useCreateTeamBindingMutation, + useDeletecollectionTeamBindingMutation, + useGetTeamBindingQuery, + useReplaceTeamBindingMutation, + useDeleteTeamBindingMutation, + useUpdateTeamBindingMutation, + useListTeamQuery, + useCreateTeamMutation, + useDeletecollectionTeamMutation, + useGetTeamQuery, + useReplaceTeamMutation, + useDeleteTeamMutation, + useUpdateTeamMutation, + useGetTeamMembersQuery, + useListUserQuery, + useCreateUserMutation, + useDeletecollectionUserMutation, + useGetUserQuery, + useReplaceUserMutation, + useDeleteUserMutation, + useUpdateUserMutation, + useGetUserTeamsQuery, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/legacy/baseAPI.ts b/packages/grafana-api-clients/src/clients/rtkq/legacy/baseAPI.ts new file mode 100644 index 00000000000..a9a3a0c84bb --- /dev/null +++ b/packages/grafana-api-clients/src/clients/rtkq/legacy/baseAPI.ts @@ -0,0 +1,9 @@ +import { createApi } from '@reduxjs/toolkit/query/react'; + +import { createBaseQuery } from '../createBaseQuery'; + +export const api = createApi({ + reducerPath: 'legacyAPI', + baseQuery: createBaseQuery({ baseURL: '/api' }), + endpoints: () => ({}), +}); diff --git a/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts new file mode 100644 index 00000000000..eb201201379 --- /dev/null +++ b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts @@ -0,0 +1,6820 @@ +import { api } from './baseAPI'; +export const addTagTypes = [ + 'enterprise', + 'access_control', + 'ldap_debug', + 'admin_ldap', + 'access_control_provisioning', + 'admin_provisioning', + 'admin', + 'admin_users', + 'quota', + 'annotations', + 'devices', + 'migrations', + 'convert_prometheus', + 'dashboards', + 'snapshots', + 'dashboard_public', + 'permissions', + 'versions', + 'datasources', + 'correlations', + 'health', + 'folders', + 'group_attribute_sync', + 'library_elements', + 'licensing', + 'saml', + 'org', + 'invites', + 'preferences', + 'orgs', + 'playlists', + 'query_history', + 'recording_rules', + 'reports', + 'search', + 'service_accounts', + 'signing_keys', + 'teams', + 'sync_team_groups', + 'signed_in_user', + 'user', + 'users', + 'provisioning', + 'sso_settings', +] as const; +const injectedRtkApi = api + .enhanceEndpoints({ + addTagTypes, + }) + .injectEndpoints({ + endpoints: (build) => ({ + searchResult: build.mutation({ + query: () => ({ url: `/access-control/assignments/search`, method: 'POST' }), + invalidatesTags: ['enterprise'], + }), + listRoles: build.query({ + query: (queryArg) => ({ + url: `/access-control/roles`, + params: { + delegatable: queryArg.delegatable, + includeHidden: queryArg.includeHidden, + }, + }), + providesTags: ['access_control', 'enterprise'], + }), + createRole: build.mutation({ + query: (queryArg) => ({ url: `/access-control/roles`, method: 'POST', body: queryArg.createRoleForm }), + invalidatesTags: ['access_control', 'enterprise'], + }), + deleteRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/roles/${queryArg.roleUid}`, + method: 'DELETE', + params: { + force: queryArg.force, + global: queryArg['global'], + }, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + getRole: build.query({ + query: (queryArg) => ({ url: `/access-control/roles/${queryArg.roleUid}` }), + providesTags: ['access_control', 'enterprise'], + }), + updateRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/roles/${queryArg.roleUid}`, + method: 'PUT', + body: queryArg.updateRoleCommand, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + getRoleAssignments: build.query({ + query: (queryArg) => ({ url: `/access-control/roles/${queryArg.roleUid}/assignments` }), + providesTags: ['access_control', 'enterprise'], + }), + setRoleAssignments: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/roles/${queryArg.roleUid}/assignments`, + method: 'PUT', + body: queryArg.setRoleAssignmentsCommand, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + getAccessControlStatus: build.query({ + query: () => ({ url: `/access-control/status` }), + providesTags: ['access_control', 'enterprise'], + }), + listTeamsRoles: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/teams/roles/search`, + method: 'POST', + body: queryArg.rolesSearchQuery, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + listTeamRoles: build.query({ + query: (queryArg) => ({ url: `/access-control/teams/${queryArg.teamId}/roles` }), + providesTags: ['access_control', 'enterprise'], + }), + addTeamRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/teams/${queryArg.teamId}/roles`, + method: 'POST', + body: queryArg.addTeamRoleCommand, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + setTeamRoles: build.mutation({ + query: (queryArg) => ({ url: `/access-control/teams/${queryArg.teamId}/roles`, method: 'PUT' }), + invalidatesTags: ['access_control', 'enterprise'], + }), + removeTeamRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/teams/${queryArg.teamId}/roles/${queryArg.roleUid}`, + method: 'DELETE', + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + listUsersRoles: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/users/roles/search`, + method: 'POST', + body: queryArg.rolesSearchQuery, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + listUserRoles: build.query({ + query: (queryArg) => ({ url: `/access-control/users/${queryArg.userId}/roles` }), + providesTags: ['access_control', 'enterprise'], + }), + addUserRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/users/${queryArg.userId}/roles`, + method: 'POST', + body: queryArg.addUserRoleCommand, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + setUserRoles: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/users/${queryArg.userId}/roles`, + method: 'PUT', + body: queryArg.setUserRolesCommand, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + removeUserRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/users/${queryArg.userId}/roles/${queryArg.roleUid}`, + method: 'DELETE', + params: { + global: queryArg['global'], + }, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + getResourceDescription: build.query({ + query: (queryArg) => ({ url: `/access-control/${queryArg.resource}/description` }), + providesTags: ['access_control'], + }), + getResourcePermissions: build.query({ + query: (queryArg) => ({ url: `/access-control/${queryArg.resource}/${queryArg.resourceId}` }), + providesTags: ['access_control'], + }), + setResourcePermissions: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/${queryArg.resource}/${queryArg.resourceId}`, + method: 'POST', + body: queryArg.setPermissionsCommand, + }), + invalidatesTags: ['access_control'], + }), + setResourcePermissionsForBuiltInRole: build.mutation< + SetResourcePermissionsForBuiltInRoleApiResponse, + SetResourcePermissionsForBuiltInRoleApiArg + >({ + query: (queryArg) => ({ + url: `/access-control/${queryArg.resource}/${queryArg.resourceId}/builtInRoles/${queryArg.builtInRole}`, + method: 'POST', + body: queryArg.setPermissionCommand, + }), + invalidatesTags: ['access_control'], + }), + setResourcePermissionsForTeam: build.mutation< + SetResourcePermissionsForTeamApiResponse, + SetResourcePermissionsForTeamApiArg + >({ + query: (queryArg) => ({ + url: `/access-control/${queryArg.resource}/${queryArg.resourceId}/teams/${queryArg.teamId}`, + method: 'POST', + body: queryArg.setPermissionCommand, + }), + invalidatesTags: ['access_control'], + }), + setResourcePermissionsForUser: build.mutation< + SetResourcePermissionsForUserApiResponse, + SetResourcePermissionsForUserApiArg + >({ + query: (queryArg) => ({ + url: `/access-control/${queryArg.resource}/${queryArg.resourceId}/users/${queryArg.userId}`, + method: 'POST', + body: queryArg.setPermissionCommand, + }), + invalidatesTags: ['access_control'], + }), + getSyncStatus: build.query({ + query: () => ({ url: `/admin/ldap-sync-status` }), + providesTags: ['ldap_debug', 'enterprise'], + }), + reloadLdapCfg: build.mutation({ + query: () => ({ url: `/admin/ldap/reload`, method: 'POST' }), + invalidatesTags: ['admin_ldap'], + }), + getLdapStatus: build.query({ + query: () => ({ url: `/admin/ldap/status` }), + providesTags: ['admin_ldap'], + }), + postSyncUserWithLdap: build.mutation({ + query: (queryArg) => ({ url: `/admin/ldap/sync/${queryArg.userId}`, method: 'POST' }), + invalidatesTags: ['admin_ldap'], + }), + getUserFromLdap: build.query({ + query: (queryArg) => ({ url: `/admin/ldap/${queryArg.userName}` }), + providesTags: ['admin_ldap'], + }), + adminProvisioningReloadAccessControl: build.mutation< + AdminProvisioningReloadAccessControlApiResponse, + AdminProvisioningReloadAccessControlApiArg + >({ + query: () => ({ url: `/admin/provisioning/access-control/reload`, method: 'POST' }), + invalidatesTags: ['access_control_provisioning', 'enterprise'], + }), + adminProvisioningReloadDashboards: build.mutation< + AdminProvisioningReloadDashboardsApiResponse, + AdminProvisioningReloadDashboardsApiArg + >({ + query: () => ({ url: `/admin/provisioning/dashboards/reload`, method: 'POST' }), + invalidatesTags: ['admin_provisioning'], + }), + adminProvisioningReloadDatasources: build.mutation< + AdminProvisioningReloadDatasourcesApiResponse, + AdminProvisioningReloadDatasourcesApiArg + >({ + query: () => ({ url: `/admin/provisioning/datasources/reload`, method: 'POST' }), + invalidatesTags: ['admin_provisioning'], + }), + adminProvisioningReloadPlugins: build.mutation< + AdminProvisioningReloadPluginsApiResponse, + AdminProvisioningReloadPluginsApiArg + >({ + query: () => ({ url: `/admin/provisioning/plugins/reload`, method: 'POST' }), + invalidatesTags: ['admin_provisioning'], + }), + adminGetSettings: build.query({ + query: () => ({ url: `/admin/settings` }), + providesTags: ['admin'], + }), + adminGetStats: build.query({ + query: () => ({ url: `/admin/stats` }), + providesTags: ['admin'], + }), + adminCreateUser: build.mutation({ + query: (queryArg) => ({ url: `/admin/users`, method: 'POST', body: queryArg.adminCreateUserForm }), + invalidatesTags: ['admin_users'], + }), + adminDeleteUser: build.mutation({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}`, method: 'DELETE' }), + invalidatesTags: ['admin_users'], + }), + adminGetUserAuthTokens: build.query({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}/auth-tokens` }), + providesTags: ['admin_users'], + }), + adminDisableUser: build.mutation({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}/disable`, method: 'POST' }), + invalidatesTags: ['admin_users'], + }), + adminEnableUser: build.mutation({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}/enable`, method: 'POST' }), + invalidatesTags: ['admin_users'], + }), + adminLogoutUser: build.mutation({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}/logout`, method: 'POST' }), + invalidatesTags: ['admin_users'], + }), + adminUpdateUserPassword: build.mutation({ + query: (queryArg) => ({ + url: `/admin/users/${queryArg.userId}/password`, + method: 'PUT', + body: queryArg.adminUpdateUserPasswordForm, + }), + invalidatesTags: ['admin_users'], + }), + adminUpdateUserPermissions: build.mutation< + AdminUpdateUserPermissionsApiResponse, + AdminUpdateUserPermissionsApiArg + >({ + query: (queryArg) => ({ + url: `/admin/users/${queryArg.userId}/permissions`, + method: 'PUT', + body: queryArg.adminUpdateUserPermissionsForm, + }), + invalidatesTags: ['admin_users'], + }), + getUserQuota: build.query({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}/quotas` }), + providesTags: ['quota', 'admin_users'], + }), + updateUserQuota: build.mutation({ + query: (queryArg) => ({ + url: `/admin/users/${queryArg.userId}/quotas/${queryArg.quotaTarget}`, + method: 'PUT', + body: queryArg.updateQuotaCmd, + }), + invalidatesTags: ['quota', 'admin_users'], + }), + adminRevokeUserAuthToken: build.mutation({ + query: (queryArg) => ({ + url: `/admin/users/${queryArg.userId}/revoke-auth-token`, + method: 'POST', + body: queryArg.revokeAuthTokenCmd, + }), + invalidatesTags: ['admin_users'], + }), + getAnnotations: build.query({ + query: (queryArg) => ({ + url: `/annotations`, + params: { + from: queryArg['from'], + to: queryArg.to, + userId: queryArg.userId, + alertId: queryArg.alertId, + alertUID: queryArg.alertUid, + dashboardId: queryArg.dashboardId, + dashboardUID: queryArg.dashboardUid, + panelId: queryArg.panelId, + limit: queryArg.limit, + tags: queryArg.tags, + type: queryArg['type'], + matchAny: queryArg.matchAny, + }, + }), + providesTags: ['annotations'], + }), + postAnnotation: build.mutation({ + query: (queryArg) => ({ url: `/annotations`, method: 'POST', body: queryArg.postAnnotationsCmd }), + invalidatesTags: ['annotations'], + }), + postGraphiteAnnotation: build.mutation({ + query: (queryArg) => ({ + url: `/annotations/graphite`, + method: 'POST', + body: queryArg.postGraphiteAnnotationsCmd, + }), + invalidatesTags: ['annotations'], + }), + massDeleteAnnotations: build.mutation({ + query: (queryArg) => ({ + url: `/annotations/mass-delete`, + method: 'POST', + body: queryArg.massDeleteAnnotationsCmd, + }), + invalidatesTags: ['annotations'], + }), + getAnnotationTags: build.query({ + query: (queryArg) => ({ + url: `/annotations/tags`, + params: { + tag: queryArg.tag, + limit: queryArg.limit, + }, + }), + providesTags: ['annotations'], + }), + deleteAnnotationById: build.mutation({ + query: (queryArg) => ({ url: `/annotations/${queryArg.annotationId}`, method: 'DELETE' }), + invalidatesTags: ['annotations'], + }), + getAnnotationById: build.query({ + query: (queryArg) => ({ url: `/annotations/${queryArg.annotationId}` }), + providesTags: ['annotations'], + }), + patchAnnotation: build.mutation({ + query: (queryArg) => ({ + url: `/annotations/${queryArg.annotationId}`, + method: 'PATCH', + body: queryArg.patchAnnotationsCmd, + }), + invalidatesTags: ['annotations'], + }), + updateAnnotation: build.mutation({ + query: (queryArg) => ({ + url: `/annotations/${queryArg.annotationId}`, + method: 'PUT', + body: queryArg.updateAnnotationsCmd, + }), + invalidatesTags: ['annotations'], + }), + listDevices: build.query({ + query: () => ({ url: `/anonymous/devices` }), + providesTags: ['devices'], + }), + searchDevices: build.query({ + query: () => ({ url: `/anonymous/search` }), + providesTags: ['devices'], + }), + getSessionList: build.query({ + query: () => ({ url: `/cloudmigration/migration` }), + providesTags: ['migrations'], + }), + createSession: build.mutation({ + query: (queryArg) => ({ + url: `/cloudmigration/migration`, + method: 'POST', + body: queryArg.cloudMigrationSessionRequestDto, + }), + invalidatesTags: ['migrations'], + }), + deleteSession: build.mutation({ + query: (queryArg) => ({ url: `/cloudmigration/migration/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['migrations'], + }), + getSession: build.query({ + query: (queryArg) => ({ url: `/cloudmigration/migration/${queryArg.uid}` }), + providesTags: ['migrations'], + }), + createSnapshot: build.mutation({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshot`, + method: 'POST', + body: queryArg.createSnapshotRequestDto, + }), + invalidatesTags: ['migrations'], + }), + getSnapshot: build.query({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshot/${queryArg.snapshotUid}`, + params: { + resultPage: queryArg.resultPage, + resultLimit: queryArg.resultLimit, + resultSortColumn: queryArg.resultSortColumn, + resultSortOrder: queryArg.resultSortOrder, + errorsOnly: queryArg.errorsOnly, + }, + }), + providesTags: ['migrations'], + }), + cancelSnapshot: build.mutation({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshot/${queryArg.snapshotUid}/cancel`, + method: 'POST', + }), + invalidatesTags: ['migrations'], + }), + uploadSnapshot: build.mutation({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshot/${queryArg.snapshotUid}/upload`, + method: 'POST', + }), + invalidatesTags: ['migrations'], + }), + getShapshotList: build.query({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshots`, + params: { + page: queryArg.page, + limit: queryArg.limit, + sort: queryArg.sort, + }, + }), + providesTags: ['migrations'], + }), + getResourceDependencies: build.query({ + query: () => ({ url: `/cloudmigration/resources/dependencies` }), + providesTags: ['migrations'], + }), + getCloudMigrationToken: build.query({ + query: () => ({ url: `/cloudmigration/token` }), + providesTags: ['migrations'], + }), + createCloudMigrationToken: build.mutation({ + query: () => ({ url: `/cloudmigration/token`, method: 'POST' }), + invalidatesTags: ['migrations'], + }), + deleteCloudMigrationToken: build.mutation({ + query: (queryArg) => ({ url: `/cloudmigration/token/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['migrations'], + }), + routeConvertPrometheusCortexGetRules: build.query< + RouteConvertPrometheusCortexGetRulesApiResponse, + RouteConvertPrometheusCortexGetRulesApiArg + >({ + query: () => ({ url: `/convert/api/prom/rules` }), + providesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexPostRuleGroups: build.mutation< + RouteConvertPrometheusCortexPostRuleGroupsApiResponse, + RouteConvertPrometheusCortexPostRuleGroupsApiArg + >({ + query: () => ({ url: `/convert/api/prom/rules`, method: 'POST' }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexDeleteNamespace: build.mutation< + RouteConvertPrometheusCortexDeleteNamespaceApiResponse, + RouteConvertPrometheusCortexDeleteNamespaceApiArg + >({ + query: (queryArg) => ({ url: `/convert/api/prom/rules/${queryArg.namespaceTitle}`, method: 'DELETE' }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexGetNamespace: build.query< + RouteConvertPrometheusCortexGetNamespaceApiResponse, + RouteConvertPrometheusCortexGetNamespaceApiArg + >({ + query: (queryArg) => ({ url: `/convert/api/prom/rules/${queryArg.namespaceTitle}` }), + providesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexPostRuleGroup: build.mutation< + RouteConvertPrometheusCortexPostRuleGroupApiResponse, + RouteConvertPrometheusCortexPostRuleGroupApiArg + >({ + query: (queryArg) => ({ + url: `/convert/api/prom/rules/${queryArg.namespaceTitle}`, + method: 'POST', + body: queryArg.prometheusRuleGroup, + headers: { + 'x-grafana-alerting-datasource-uid': queryArg['x-grafana-alerting-datasource-uid'], + 'x-grafana-alerting-recording-rules-paused': queryArg['x-grafana-alerting-recording-rules-paused'], + 'x-grafana-alerting-alert-rules-paused': queryArg['x-grafana-alerting-alert-rules-paused'], + 'x-grafana-alerting-target-datasource-uid': queryArg['x-grafana-alerting-target-datasource-uid'], + 'x-grafana-alerting-folder-uid': queryArg['x-grafana-alerting-folder-uid'], + 'x-grafana-alerting-notification-receiver': queryArg['x-grafana-alerting-notification-receiver'], + }, + }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexDeleteRuleGroup: build.mutation< + RouteConvertPrometheusCortexDeleteRuleGroupApiResponse, + RouteConvertPrometheusCortexDeleteRuleGroupApiArg + >({ + query: (queryArg) => ({ + url: `/convert/api/prom/rules/${queryArg.namespaceTitle}/${queryArg.group}`, + method: 'DELETE', + }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexGetRuleGroup: build.query< + RouteConvertPrometheusCortexGetRuleGroupApiResponse, + RouteConvertPrometheusCortexGetRuleGroupApiArg + >({ + query: (queryArg) => ({ url: `/convert/api/prom/rules/${queryArg.namespaceTitle}/${queryArg.group}` }), + providesTags: ['convert_prometheus'], + }), + routeConvertPrometheusGetRules: build.query< + RouteConvertPrometheusGetRulesApiResponse, + RouteConvertPrometheusGetRulesApiArg + >({ + query: () => ({ url: `/convert/prometheus/config/v1/rules` }), + providesTags: ['convert_prometheus'], + }), + routeConvertPrometheusPostRuleGroups: build.mutation< + RouteConvertPrometheusPostRuleGroupsApiResponse, + RouteConvertPrometheusPostRuleGroupsApiArg + >({ + query: () => ({ url: `/convert/prometheus/config/v1/rules`, method: 'POST' }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusDeleteNamespace: build.mutation< + RouteConvertPrometheusDeleteNamespaceApiResponse, + RouteConvertPrometheusDeleteNamespaceApiArg + >({ + query: (queryArg) => ({ + url: `/convert/prometheus/config/v1/rules/${queryArg.namespaceTitle}`, + method: 'DELETE', + }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusGetNamespace: build.query< + RouteConvertPrometheusGetNamespaceApiResponse, + RouteConvertPrometheusGetNamespaceApiArg + >({ + query: (queryArg) => ({ url: `/convert/prometheus/config/v1/rules/${queryArg.namespaceTitle}` }), + providesTags: ['convert_prometheus'], + }), + routeConvertPrometheusPostRuleGroup: build.mutation< + RouteConvertPrometheusPostRuleGroupApiResponse, + RouteConvertPrometheusPostRuleGroupApiArg + >({ + query: (queryArg) => ({ + url: `/convert/prometheus/config/v1/rules/${queryArg.namespaceTitle}`, + method: 'POST', + body: queryArg.prometheusRuleGroup, + headers: { + 'x-grafana-alerting-datasource-uid': queryArg['x-grafana-alerting-datasource-uid'], + 'x-grafana-alerting-recording-rules-paused': queryArg['x-grafana-alerting-recording-rules-paused'], + 'x-grafana-alerting-alert-rules-paused': queryArg['x-grafana-alerting-alert-rules-paused'], + 'x-grafana-alerting-target-datasource-uid': queryArg['x-grafana-alerting-target-datasource-uid'], + 'x-grafana-alerting-folder-uid': queryArg['x-grafana-alerting-folder-uid'], + 'x-grafana-alerting-notification-receiver': queryArg['x-grafana-alerting-notification-receiver'], + }, + }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusDeleteRuleGroup: build.mutation< + RouteConvertPrometheusDeleteRuleGroupApiResponse, + RouteConvertPrometheusDeleteRuleGroupApiArg + >({ + query: (queryArg) => ({ + url: `/convert/prometheus/config/v1/rules/${queryArg.namespaceTitle}/${queryArg.group}`, + method: 'DELETE', + }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusGetRuleGroup: build.query< + RouteConvertPrometheusGetRuleGroupApiResponse, + RouteConvertPrometheusGetRuleGroupApiArg + >({ + query: (queryArg) => ({ + url: `/convert/prometheus/config/v1/rules/${queryArg.namespaceTitle}/${queryArg.group}`, + }), + providesTags: ['convert_prometheus'], + }), + searchDashboardSnapshots: build.query({ + query: (queryArg) => ({ + url: `/dashboard/snapshots`, + params: { + query: queryArg.query, + limit: queryArg.limit, + }, + }), + providesTags: ['dashboards', 'snapshots'], + }), + calculateDashboardDiff: build.mutation({ + query: (queryArg) => ({ url: `/dashboards/calculate-diff`, method: 'POST', body: queryArg.body }), + invalidatesTags: ['dashboards'], + }), + postDashboard: build.mutation({ + query: (queryArg) => ({ url: `/dashboards/db`, method: 'POST', body: queryArg.saveDashboardCommand }), + invalidatesTags: ['dashboards'], + }), + getHomeDashboard: build.query({ + query: () => ({ url: `/dashboards/home` }), + providesTags: ['dashboards'], + }), + importDashboard: build.mutation({ + query: (queryArg) => ({ url: `/dashboards/import`, method: 'POST', body: queryArg.importDashboardRequest }), + invalidatesTags: ['dashboards'], + }), + interpolateDashboard: build.mutation({ + query: () => ({ url: `/dashboards/interpolate`, method: 'POST' }), + invalidatesTags: ['dashboards'], + }), + listPublicDashboards: build.query({ + query: () => ({ url: `/dashboards/public-dashboards` }), + providesTags: ['dashboards', 'dashboard_public'], + }), + getDashboardTags: build.query({ + query: () => ({ url: `/dashboards/tags` }), + providesTags: ['dashboards'], + }), + getPublicDashboard: build.query({ + query: (queryArg) => ({ url: `/dashboards/uid/${queryArg.dashboardUid}/public-dashboards` }), + providesTags: ['dashboards', 'dashboard_public'], + }), + createPublicDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.dashboardUid}/public-dashboards`, + method: 'POST', + body: queryArg.publicDashboardDto, + }), + invalidatesTags: ['dashboards', 'dashboard_public'], + }), + deletePublicDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.dashboardUid}/public-dashboards/${queryArg.uid}`, + method: 'DELETE', + }), + invalidatesTags: ['dashboards', 'dashboard_public'], + }), + updatePublicDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.dashboardUid}/public-dashboards/${queryArg.uid}`, + method: 'PATCH', + body: queryArg.publicDashboardDto, + }), + invalidatesTags: ['dashboards', 'dashboard_public'], + }), + deleteDashboardByUid: build.mutation({ + query: (queryArg) => ({ url: `/dashboards/uid/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['dashboards'], + }), + getDashboardByUid: build.query({ + query: (queryArg) => ({ url: `/dashboards/uid/${queryArg.uid}` }), + providesTags: ['dashboards'], + }), + getDashboardPermissionsListByUid: build.query< + GetDashboardPermissionsListByUidApiResponse, + GetDashboardPermissionsListByUidApiArg + >({ + query: (queryArg) => ({ url: `/dashboards/uid/${queryArg.uid}/permissions` }), + providesTags: ['dashboards', 'permissions'], + }), + updateDashboardPermissionsByUid: build.mutation< + UpdateDashboardPermissionsByUidApiResponse, + UpdateDashboardPermissionsByUidApiArg + >({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.uid}/permissions`, + method: 'POST', + body: queryArg.updateDashboardAclCommand, + }), + invalidatesTags: ['dashboards', 'permissions'], + }), + restoreDashboardVersionByUid: build.mutation< + RestoreDashboardVersionByUidApiResponse, + RestoreDashboardVersionByUidApiArg + >({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.uid}/restore`, + method: 'POST', + body: queryArg.restoreDashboardVersionCommand, + }), + invalidatesTags: ['dashboards', 'versions'], + }), + getDashboardVersionsByUid: build.query({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.uid}/versions`, + params: { + limit: queryArg.limit, + start: queryArg.start, + }, + }), + providesTags: ['dashboards', 'versions'], + }), + getDashboardVersionByUid: build.query({ + query: (queryArg) => ({ url: `/dashboards/uid/${queryArg.uid}/versions/${queryArg.dashboardVersionId}` }), + providesTags: ['dashboards', 'versions'], + }), + getDataSources: build.query({ + query: () => ({ url: `/datasources` }), + providesTags: ['datasources'], + }), + addDataSource: build.mutation({ + query: (queryArg) => ({ url: `/datasources`, method: 'POST', body: queryArg.addDataSourceCommand }), + invalidatesTags: ['datasources'], + }), + getCorrelations: build.query({ + query: (queryArg) => ({ + url: `/datasources/correlations`, + params: { + limit: queryArg.limit, + page: queryArg.page, + sourceUID: queryArg.sourceUid, + }, + }), + providesTags: ['datasources', 'correlations'], + }), + getDataSourceIdByName: build.query({ + query: (queryArg) => ({ url: `/datasources/id/${queryArg.name}` }), + providesTags: ['datasources'], + }), + deleteDataSourceByName: build.mutation({ + query: (queryArg) => ({ url: `/datasources/name/${queryArg.name}`, method: 'DELETE' }), + invalidatesTags: ['datasources'], + }), + getDataSourceByName: build.query({ + query: (queryArg) => ({ url: `/datasources/name/${queryArg.name}` }), + providesTags: ['datasources'], + }), + datasourceProxyDeleteByUiDcalls: build.mutation< + DatasourceProxyDeleteByUiDcallsApiResponse, + DatasourceProxyDeleteByUiDcallsApiArg + >({ + query: (queryArg) => ({ + url: `/datasources/proxy/uid/${queryArg.uid}/${queryArg.datasourceProxyRoute}`, + method: 'DELETE', + }), + invalidatesTags: ['datasources'], + }), + datasourceProxyGetByUiDcalls: build.query< + DatasourceProxyGetByUiDcallsApiResponse, + DatasourceProxyGetByUiDcallsApiArg + >({ + query: (queryArg) => ({ url: `/datasources/proxy/uid/${queryArg.uid}/${queryArg.datasourceProxyRoute}` }), + providesTags: ['datasources'], + }), + datasourceProxyPostByUiDcalls: build.mutation< + DatasourceProxyPostByUiDcallsApiResponse, + DatasourceProxyPostByUiDcallsApiArg + >({ + query: (queryArg) => ({ + url: `/datasources/proxy/uid/${queryArg.uid}/${queryArg.datasourceProxyRoute}`, + method: 'POST', + body: queryArg.body, + }), + invalidatesTags: ['datasources'], + }), + getCorrelationsBySourceUid: build.query({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.sourceUid}/correlations` }), + providesTags: ['datasources', 'correlations'], + }), + createCorrelation: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.sourceUid}/correlations`, + method: 'POST', + body: queryArg.createCorrelationCommand, + }), + invalidatesTags: ['datasources', 'correlations'], + }), + getCorrelation: build.query({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.sourceUid}/correlations/${queryArg.correlationUid}`, + }), + providesTags: ['datasources', 'correlations'], + }), + updateCorrelation: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.sourceUid}/correlations/${queryArg.correlationUid}`, + method: 'PATCH', + body: queryArg.updateCorrelationCommand, + }), + invalidatesTags: ['datasources', 'correlations'], + }), + deleteDataSourceByUid: build.mutation({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['datasources'], + }), + getDataSourceByUid: build.query({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.uid}` }), + providesTags: ['datasources'], + }), + updateDataSourceByUid: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.uid}`, + method: 'PUT', + body: queryArg.updateDataSourceCommand, + }), + invalidatesTags: ['datasources'], + }), + deleteCorrelation: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.uid}/correlations/${queryArg.correlationUid}`, + method: 'DELETE', + }), + invalidatesTags: ['datasources', 'correlations'], + }), + checkDatasourceHealthWithUid: build.query< + CheckDatasourceHealthWithUidApiResponse, + CheckDatasourceHealthWithUidApiArg + >({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.uid}/health` }), + providesTags: ['datasources', 'health'], + }), + getTeamLbacRulesApi: build.query({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.uid}/lbac/teams` }), + providesTags: ['enterprise'], + }), + updateTeamLbacRulesApi: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.uid}/lbac/teams`, + method: 'PUT', + body: queryArg.updateTeamLbacCommand, + }), + invalidatesTags: ['enterprise'], + }), + callDatasourceResourceWithUid: build.query< + CallDatasourceResourceWithUidApiResponse, + CallDatasourceResourceWithUidApiArg + >({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.uid}/resources/${queryArg.datasourceProxyRoute}` }), + providesTags: ['datasources'], + }), + getDataSourceCacheConfig: build.query({ + query: (queryArg) => ({ url: `/datasources/${queryArg.dataSourceUid}/cache` }), + providesTags: ['enterprise'], + }), + setDataSourceCacheConfig: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/${queryArg.dataSourceUid}/cache`, + method: 'POST', + body: queryArg.cacheConfigSetter, + }), + invalidatesTags: ['enterprise'], + }), + cleanDataSourceCache: build.mutation({ + query: (queryArg) => ({ url: `/datasources/${queryArg.dataSourceUid}/cache/clean`, method: 'POST' }), + invalidatesTags: ['enterprise'], + }), + disableDataSourceCache: build.mutation({ + query: (queryArg) => ({ url: `/datasources/${queryArg.dataSourceUid}/cache/disable`, method: 'POST' }), + invalidatesTags: ['enterprise'], + }), + enableDataSourceCache: build.mutation({ + query: (queryArg) => ({ url: `/datasources/${queryArg.dataSourceUid}/cache/enable`, method: 'POST' }), + invalidatesTags: ['enterprise'], + }), + queryMetricsWithExpressions: build.mutation< + QueryMetricsWithExpressionsApiResponse, + QueryMetricsWithExpressionsApiArg + >({ + query: (queryArg) => ({ url: `/ds/query`, method: 'POST', body: queryArg.metricRequest }), + invalidatesTags: ['datasources'], + }), + getFolders: build.query({ + query: (queryArg) => ({ + url: `/folders`, + params: { + limit: queryArg.limit, + page: queryArg.page, + parentUid: queryArg.parentUid, + permission: queryArg.permission, + }, + }), + providesTags: ['folders'], + }), + createFolder: build.mutation({ + query: (queryArg) => ({ url: `/folders`, method: 'POST', body: queryArg.createFolderCommand }), + invalidatesTags: ['folders'], + }), + deleteFolder: build.mutation({ + query: (queryArg) => ({ + url: `/folders/${queryArg.folderUid}`, + method: 'DELETE', + params: { + forceDeleteRules: queryArg.forceDeleteRules, + }, + }), + invalidatesTags: ['folders'], + }), + getFolderByUid: build.query({ + query: (queryArg) => ({ url: `/folders/${queryArg.folderUid}` }), + providesTags: ['folders'], + }), + updateFolder: build.mutation({ + query: (queryArg) => ({ + url: `/folders/${queryArg.folderUid}`, + method: 'PUT', + body: queryArg.updateFolderCommand, + }), + invalidatesTags: ['folders'], + }), + getFolderDescendantCounts: build.query({ + query: (queryArg) => ({ url: `/folders/${queryArg.folderUid}/counts` }), + providesTags: ['folders'], + }), + moveFolder: build.mutation({ + query: (queryArg) => ({ + url: `/folders/${queryArg.folderUid}/move`, + method: 'POST', + body: queryArg.moveFolderCommand, + }), + invalidatesTags: ['folders'], + }), + getFolderPermissionList: build.query({ + query: (queryArg) => ({ url: `/folders/${queryArg.folderUid}/permissions` }), + providesTags: ['folders', 'permissions'], + }), + updateFolderPermissions: build.mutation({ + query: (queryArg) => ({ + url: `/folders/${queryArg.folderUid}/permissions`, + method: 'POST', + body: queryArg.updateDashboardAclCommand, + }), + invalidatesTags: ['folders', 'permissions'], + }), + getMappedGroups: build.query({ + query: () => ({ url: `/groupsync/groups` }), + providesTags: ['group_attribute_sync', 'enterprise'], + }), + deleteGroupMappings: build.mutation({ + query: (queryArg) => ({ url: `/groupsync/groups/${queryArg.groupId}`, method: 'DELETE' }), + invalidatesTags: ['group_attribute_sync', 'enterprise'], + }), + createGroupMappings: build.mutation({ + query: (queryArg) => ({ + url: `/groupsync/groups/${queryArg.groupId}`, + method: 'POST', + body: queryArg.groupAttributes, + }), + invalidatesTags: ['group_attribute_sync', 'enterprise'], + }), + updateGroupMappings: build.mutation({ + query: (queryArg) => ({ + url: `/groupsync/groups/${queryArg.groupId}`, + method: 'PUT', + body: queryArg.groupAttributes, + }), + invalidatesTags: ['group_attribute_sync', 'enterprise'], + }), + getGroupRoles: build.query({ + query: (queryArg) => ({ url: `/groupsync/groups/${queryArg.groupId}/roles` }), + providesTags: ['group_attribute_sync', 'enterprise'], + }), + getHealth: build.query({ + query: () => ({ url: `/health` }), + providesTags: ['health'], + }), + getLibraryElements: build.query({ + query: (queryArg) => ({ + url: `/library-elements`, + params: { + searchString: queryArg.searchString, + kind: queryArg.kind, + sortDirection: queryArg.sortDirection, + typeFilter: queryArg.typeFilter, + excludeUid: queryArg.excludeUid, + folderFilter: queryArg.folderFilter, + perPage: queryArg.perPage, + page: queryArg.page, + }, + }), + providesTags: ['library_elements'], + }), + createLibraryElement: build.mutation({ + query: (queryArg) => ({ url: `/library-elements`, method: 'POST', body: queryArg.createLibraryElementCommand }), + invalidatesTags: ['library_elements'], + }), + getLibraryElementByName: build.query({ + query: (queryArg) => ({ url: `/library-elements/name/${queryArg.libraryElementName}` }), + providesTags: ['library_elements'], + }), + deleteLibraryElementByUid: build.mutation({ + query: (queryArg) => ({ url: `/library-elements/${queryArg.libraryElementUid}`, method: 'DELETE' }), + invalidatesTags: ['library_elements'], + }), + getLibraryElementByUid: build.query({ + query: (queryArg) => ({ url: `/library-elements/${queryArg.libraryElementUid}` }), + providesTags: ['library_elements'], + }), + updateLibraryElement: build.mutation({ + query: (queryArg) => ({ + url: `/library-elements/${queryArg.libraryElementUid}`, + method: 'PATCH', + body: queryArg.patchLibraryElementCommand, + }), + invalidatesTags: ['library_elements'], + }), + getLibraryElementConnections: build.query< + GetLibraryElementConnectionsApiResponse, + GetLibraryElementConnectionsApiArg + >({ + query: (queryArg) => ({ url: `/library-elements/${queryArg.libraryElementUid}/connections/` }), + providesTags: ['library_elements'], + }), + getStatus: build.query({ + query: () => ({ url: `/licensing/check` }), + providesTags: ['licensing', 'enterprise'], + }), + refreshLicenseStats: build.query({ + query: () => ({ url: `/licensing/refresh-stats` }), + providesTags: ['licensing', 'enterprise'], + }), + deleteLicenseToken: build.mutation({ + query: (queryArg) => ({ url: `/licensing/token`, method: 'DELETE', body: queryArg.deleteTokenCommand }), + invalidatesTags: ['licensing', 'enterprise'], + }), + getLicenseToken: build.query({ + query: () => ({ url: `/licensing/token` }), + providesTags: ['licensing', 'enterprise'], + }), + postLicenseToken: build.mutation({ + query: (queryArg) => ({ url: `/licensing/token`, method: 'POST', body: queryArg.deleteTokenCommand }), + invalidatesTags: ['licensing', 'enterprise'], + }), + postRenewLicenseToken: build.mutation({ + query: (queryArg) => ({ url: `/licensing/token/renew`, method: 'POST', body: queryArg.body }), + invalidatesTags: ['licensing', 'enterprise'], + }), + getSamlLogout: build.query({ + query: () => ({ url: `/logout/saml` }), + providesTags: ['saml', 'enterprise'], + }), + getCurrentOrg: build.query({ + query: () => ({ url: `/org` }), + providesTags: ['org'], + }), + updateCurrentOrg: build.mutation({ + query: (queryArg) => ({ url: `/org`, method: 'PUT', body: queryArg.updateOrgForm }), + invalidatesTags: ['org'], + }), + updateCurrentOrgAddress: build.mutation({ + query: (queryArg) => ({ url: `/org/address`, method: 'PUT', body: queryArg.updateOrgAddressForm }), + invalidatesTags: ['org'], + }), + getPendingOrgInvites: build.query({ + query: () => ({ url: `/org/invites` }), + providesTags: ['org', 'invites'], + }), + addOrgInvite: build.mutation({ + query: (queryArg) => ({ url: `/org/invites`, method: 'POST', body: queryArg.addInviteForm }), + invalidatesTags: ['org', 'invites'], + }), + revokeInvite: build.mutation({ + query: (queryArg) => ({ url: `/org/invites/${queryArg.invitationCode}/revoke`, method: 'DELETE' }), + invalidatesTags: ['org', 'invites'], + }), + getOrgPreferences: build.query({ + query: () => ({ url: `/org/preferences` }), + providesTags: ['org', 'preferences'], + }), + patchOrgPreferences: build.mutation({ + query: (queryArg) => ({ url: `/org/preferences`, method: 'PATCH', body: queryArg.patchPrefsCmd }), + invalidatesTags: ['org', 'preferences'], + }), + updateOrgPreferences: build.mutation({ + query: (queryArg) => ({ url: `/org/preferences`, method: 'PUT', body: queryArg.updatePrefsCmd }), + invalidatesTags: ['org', 'preferences'], + }), + getCurrentOrgQuota: build.query({ + query: () => ({ url: `/org/quotas` }), + providesTags: ['quota', 'org'], + }), + getOrgUsersForCurrentOrg: build.query({ + query: (queryArg) => ({ + url: `/org/users`, + params: { + query: queryArg.query, + limit: queryArg.limit, + }, + }), + providesTags: ['org'], + }), + addOrgUserToCurrentOrg: build.mutation({ + query: (queryArg) => ({ url: `/org/users`, method: 'POST', body: queryArg.addOrgUserCommand }), + invalidatesTags: ['org'], + }), + getOrgUsersForCurrentOrgLookup: build.query< + GetOrgUsersForCurrentOrgLookupApiResponse, + GetOrgUsersForCurrentOrgLookupApiArg + >({ + query: (queryArg) => ({ + url: `/org/users/lookup`, + params: { + query: queryArg.query, + limit: queryArg.limit, + }, + }), + providesTags: ['org'], + }), + removeOrgUserForCurrentOrg: build.mutation< + RemoveOrgUserForCurrentOrgApiResponse, + RemoveOrgUserForCurrentOrgApiArg + >({ + query: (queryArg) => ({ url: `/org/users/${queryArg.userId}`, method: 'DELETE' }), + invalidatesTags: ['org'], + }), + updateOrgUserForCurrentOrg: build.mutation< + UpdateOrgUserForCurrentOrgApiResponse, + UpdateOrgUserForCurrentOrgApiArg + >({ + query: (queryArg) => ({ + url: `/org/users/${queryArg.userId}`, + method: 'PATCH', + body: queryArg.updateOrgUserCommand, + }), + invalidatesTags: ['org'], + }), + searchOrgs: build.query({ + query: (queryArg) => ({ + url: `/orgs`, + params: { + page: queryArg.page, + perpage: queryArg.perpage, + name: queryArg.name, + query: queryArg.query, + }, + }), + providesTags: ['orgs'], + }), + createOrg: build.mutation({ + query: (queryArg) => ({ url: `/orgs`, method: 'POST', body: queryArg.createOrgCommand }), + invalidatesTags: ['orgs'], + }), + getOrgByName: build.query({ + query: (queryArg) => ({ url: `/orgs/name/${queryArg.orgName}` }), + providesTags: ['orgs'], + }), + deleteOrgById: build.mutation({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}`, method: 'DELETE' }), + invalidatesTags: ['orgs'], + }), + getOrgById: build.query({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}` }), + providesTags: ['orgs'], + }), + updateOrg: build.mutation({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}`, method: 'PUT', body: queryArg.updateOrgForm }), + invalidatesTags: ['orgs'], + }), + updateOrgAddress: build.mutation({ + query: (queryArg) => ({ + url: `/orgs/${queryArg.orgId}/address`, + method: 'PUT', + body: queryArg.updateOrgAddressForm, + }), + invalidatesTags: ['orgs'], + }), + getOrgQuota: build.query({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}/quotas` }), + providesTags: ['quota', 'orgs'], + }), + updateOrgQuota: build.mutation({ + query: (queryArg) => ({ + url: `/orgs/${queryArg.orgId}/quotas/${queryArg.quotaTarget}`, + method: 'PUT', + body: queryArg.updateQuotaCmd, + }), + invalidatesTags: ['quota', 'orgs'], + }), + getOrgUsers: build.query({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}/users` }), + providesTags: ['orgs'], + }), + addOrgUser: build.mutation({ + query: (queryArg) => ({ + url: `/orgs/${queryArg.orgId}/users`, + method: 'POST', + body: queryArg.addOrgUserCommand, + }), + invalidatesTags: ['orgs'], + }), + searchOrgUsers: build.query({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}/users/search` }), + providesTags: ['orgs'], + }), + removeOrgUser: build.mutation({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}/users/${queryArg.userId}`, method: 'DELETE' }), + invalidatesTags: ['orgs'], + }), + updateOrgUser: build.mutation({ + query: (queryArg) => ({ + url: `/orgs/${queryArg.orgId}/users/${queryArg.userId}`, + method: 'PATCH', + body: queryArg.updateOrgUserCommand, + }), + invalidatesTags: ['orgs'], + }), + searchPlaylists: build.query({ + query: (queryArg) => ({ + url: `/playlists`, + params: { + query: queryArg.query, + limit: queryArg.limit, + }, + }), + providesTags: ['playlists'], + }), + createPlaylist: build.mutation({ + query: (queryArg) => ({ url: `/playlists`, method: 'POST', body: queryArg.createPlaylistCommand }), + invalidatesTags: ['playlists'], + }), + deletePlaylist: build.mutation({ + query: (queryArg) => ({ url: `/playlists/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['playlists'], + }), + getPlaylist: build.query({ + query: (queryArg) => ({ url: `/playlists/${queryArg.uid}` }), + providesTags: ['playlists'], + }), + updatePlaylist: build.mutation({ + query: (queryArg) => ({ + url: `/playlists/${queryArg.uid}`, + method: 'PUT', + body: queryArg.updatePlaylistCommand, + }), + invalidatesTags: ['playlists'], + }), + getPlaylistItems: build.query({ + query: (queryArg) => ({ url: `/playlists/${queryArg.uid}/items` }), + providesTags: ['playlists'], + }), + viewPublicDashboard: build.query({ + query: (queryArg) => ({ url: `/public/dashboards/${queryArg.accessToken}` }), + providesTags: ['dashboards', 'dashboard_public'], + }), + getPublicAnnotations: build.query({ + query: (queryArg) => ({ url: `/public/dashboards/${queryArg.accessToken}/annotations` }), + providesTags: ['dashboards', 'annotations', 'dashboard_public'], + }), + queryPublicDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/public/dashboards/${queryArg.accessToken}/panels/${queryArg.panelId}/query`, + method: 'POST', + }), + invalidatesTags: ['dashboards', 'dashboard_public'], + }), + searchQueries: build.query({ + query: (queryArg) => ({ + url: `/query-history`, + params: { + datasourceUid: queryArg.datasourceUid, + searchString: queryArg.searchString, + onlyStarred: queryArg.onlyStarred, + sort: queryArg.sort, + page: queryArg.page, + limit: queryArg.limit, + from: queryArg['from'], + to: queryArg.to, + }, + }), + providesTags: ['query_history'], + }), + createQuery: build.mutation({ + query: (queryArg) => ({ + url: `/query-history`, + method: 'POST', + body: queryArg.createQueryInQueryHistoryCommand, + }), + invalidatesTags: ['query_history'], + }), + unstarQuery: build.mutation({ + query: (queryArg) => ({ url: `/query-history/star/${queryArg.queryHistoryUid}`, method: 'DELETE' }), + invalidatesTags: ['query_history'], + }), + starQuery: build.mutation({ + query: (queryArg) => ({ url: `/query-history/star/${queryArg.queryHistoryUid}`, method: 'POST' }), + invalidatesTags: ['query_history'], + }), + deleteQuery: build.mutation({ + query: (queryArg) => ({ url: `/query-history/${queryArg.queryHistoryUid}`, method: 'DELETE' }), + invalidatesTags: ['query_history'], + }), + patchQueryComment: build.mutation({ + query: (queryArg) => ({ + url: `/query-history/${queryArg.queryHistoryUid}`, + method: 'PATCH', + body: queryArg.patchQueryCommentInQueryHistoryCommand, + }), + invalidatesTags: ['query_history'], + }), + listRecordingRules: build.query({ + query: () => ({ url: `/recording-rules` }), + providesTags: ['recording_rules', 'enterprise'], + }), + createRecordingRule: build.mutation({ + query: (queryArg) => ({ url: `/recording-rules`, method: 'POST', body: queryArg.recordingRuleJson }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + updateRecordingRule: build.mutation({ + query: (queryArg) => ({ url: `/recording-rules`, method: 'PUT', body: queryArg.recordingRuleJson }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + testCreateRecordingRule: build.mutation({ + query: (queryArg) => ({ url: `/recording-rules/test`, method: 'POST', body: queryArg.recordingRuleJson }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + deleteRecordingRuleWriteTarget: build.mutation< + DeleteRecordingRuleWriteTargetApiResponse, + DeleteRecordingRuleWriteTargetApiArg + >({ + query: () => ({ url: `/recording-rules/writer`, method: 'DELETE' }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + getRecordingRuleWriteTarget: build.query< + GetRecordingRuleWriteTargetApiResponse, + GetRecordingRuleWriteTargetApiArg + >({ + query: () => ({ url: `/recording-rules/writer` }), + providesTags: ['recording_rules', 'enterprise'], + }), + createRecordingRuleWriteTarget: build.mutation< + CreateRecordingRuleWriteTargetApiResponse, + CreateRecordingRuleWriteTargetApiArg + >({ + query: (queryArg) => ({ + url: `/recording-rules/writer`, + method: 'POST', + body: queryArg.prometheusRemoteWriteTargetJson, + }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + deleteRecordingRule: build.mutation({ + query: (queryArg) => ({ url: `/recording-rules/${queryArg.recordingRuleId}`, method: 'DELETE' }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + getReports: build.query({ + query: () => ({ url: `/reports` }), + providesTags: ['reports', 'enterprise'], + }), + createReport: build.mutation({ + query: (queryArg) => ({ url: `/reports`, method: 'POST', body: queryArg.createOrUpdateReport }), + invalidatesTags: ['reports', 'enterprise'], + }), + getReportsByDashboardUid: build.query({ + query: (queryArg) => ({ url: `/reports/dashboards/${queryArg.uid}` }), + providesTags: ['reports', 'enterprise'], + }), + sendReport: build.mutation({ + query: (queryArg) => ({ url: `/reports/email`, method: 'POST', body: queryArg.reportEmail }), + invalidatesTags: ['reports', 'enterprise'], + }), + getSettingsImage: build.query({ + query: () => ({ url: `/reports/images/:image` }), + providesTags: ['reports', 'enterprise'], + }), + renderReportCsVs: build.query({ + query: (queryArg) => ({ + url: `/reports/render/csvs`, + params: { + dashboards: queryArg.dashboards, + title: queryArg.title, + }, + }), + providesTags: ['reports', 'enterprise'], + }), + renderReportPdFs: build.query({ + query: (queryArg) => ({ + url: `/reports/render/pdfs`, + params: { + dashboards: queryArg.dashboards, + orientation: queryArg.orientation, + layout: queryArg.layout, + title: queryArg.title, + scaleFactor: queryArg.scaleFactor, + includeTables: queryArg.includeTables, + }, + }), + providesTags: ['reports', 'enterprise'], + }), + getReportSettings: build.query({ + query: () => ({ url: `/reports/settings` }), + providesTags: ['reports', 'enterprise'], + }), + saveReportSettings: build.mutation({ + query: (queryArg) => ({ url: `/reports/settings`, method: 'POST', body: queryArg.reportSettings }), + invalidatesTags: ['reports', 'enterprise'], + }), + sendTestEmail: build.mutation({ + query: (queryArg) => ({ url: `/reports/test-email`, method: 'POST', body: queryArg.createOrUpdateReport }), + invalidatesTags: ['reports', 'enterprise'], + }), + postAcs: build.mutation({ + query: (queryArg) => ({ + url: `/saml/acs`, + method: 'POST', + params: { + RelayState: queryArg.relayState, + }, + }), + invalidatesTags: ['saml', 'enterprise'], + }), + getMetadata: build.query({ + query: () => ({ url: `/saml/metadata` }), + providesTags: ['saml', 'enterprise'], + }), + getSlo: build.query({ + query: () => ({ url: `/saml/slo` }), + providesTags: ['saml', 'enterprise'], + }), + postSlo: build.mutation({ + query: (queryArg) => ({ + url: `/saml/slo`, + method: 'POST', + params: { + SAMLRequest: queryArg.samlRequest, + SAMLResponse: queryArg.samlResponse, + }, + }), + invalidatesTags: ['saml', 'enterprise'], + }), + search: build.query({ + query: (queryArg) => ({ + url: `/search`, + params: { + query: queryArg.query, + tag: queryArg.tag, + type: queryArg['type'], + dashboardIds: queryArg.dashboardIds, + dashboardUIDs: queryArg.dashboardUiDs, + folderIds: queryArg.folderIds, + folderUIDs: queryArg.folderUiDs, + starred: queryArg.starred, + limit: queryArg.limit, + page: queryArg.page, + permission: queryArg.permission, + sort: queryArg.sort, + deleted: queryArg.deleted, + }, + }), + providesTags: ['search'], + }), + listSortOptions: build.query({ + query: () => ({ url: `/search/sorting` }), + providesTags: ['search'], + }), + createServiceAccount: build.mutation({ + query: (queryArg) => ({ url: `/serviceaccounts`, method: 'POST', body: queryArg.createServiceAccountForm }), + invalidatesTags: ['service_accounts'], + }), + searchOrgServiceAccountsWithPaging: build.query< + SearchOrgServiceAccountsWithPagingApiResponse, + SearchOrgServiceAccountsWithPagingApiArg + >({ + query: (queryArg) => ({ + url: `/serviceaccounts/search`, + params: { + Disabled: queryArg.disabled, + expiredTokens: queryArg.expiredTokens, + query: queryArg.query, + perpage: queryArg.perpage, + page: queryArg.page, + }, + }), + providesTags: ['service_accounts'], + }), + deleteServiceAccount: build.mutation({ + query: (queryArg) => ({ url: `/serviceaccounts/${queryArg.serviceAccountId}`, method: 'DELETE' }), + invalidatesTags: ['service_accounts'], + }), + retrieveServiceAccount: build.query({ + query: (queryArg) => ({ url: `/serviceaccounts/${queryArg.serviceAccountId}` }), + providesTags: ['service_accounts'], + }), + updateServiceAccount: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.serviceAccountId}`, + method: 'PATCH', + body: queryArg.updateServiceAccountForm, + }), + invalidatesTags: ['service_accounts'], + }), + listTokens: build.query({ + query: (queryArg) => ({ url: `/serviceaccounts/${queryArg.serviceAccountId}/tokens` }), + providesTags: ['service_accounts'], + }), + createToken: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.serviceAccountId}/tokens`, + method: 'POST', + body: queryArg.addServiceAccountTokenCommand, + }), + invalidatesTags: ['service_accounts'], + }), + deleteToken: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.serviceAccountId}/tokens/${queryArg.tokenId}`, + method: 'DELETE', + }), + invalidatesTags: ['service_accounts'], + }), + retrieveJwks: build.query({ + query: () => ({ url: `/signing-keys/keys` }), + providesTags: ['signing_keys'], + }), + getSharingOptions: build.query({ + query: () => ({ url: `/snapshot/shared-options` }), + providesTags: ['snapshots'], + }), + createDashboardSnapshot: build.mutation({ + query: (queryArg) => ({ url: `/snapshots`, method: 'POST', body: queryArg.createDashboardSnapshotCommand }), + invalidatesTags: ['dashboards', 'snapshots'], + }), + deleteDashboardSnapshotByDeleteKey: build.query< + DeleteDashboardSnapshotByDeleteKeyApiResponse, + DeleteDashboardSnapshotByDeleteKeyApiArg + >({ + query: (queryArg) => ({ url: `/snapshots-delete/${queryArg.deleteKey}` }), + providesTags: ['dashboards', 'snapshots'], + }), + deleteDashboardSnapshot: build.mutation({ + query: (queryArg) => ({ url: `/snapshots/${queryArg.key}`, method: 'DELETE' }), + invalidatesTags: ['dashboards', 'snapshots'], + }), + getDashboardSnapshot: build.query({ + query: (queryArg) => ({ url: `/snapshots/${queryArg.key}` }), + providesTags: ['dashboards', 'snapshots'], + }), + createTeam: build.mutation({ + query: (queryArg) => ({ url: `/teams`, method: 'POST', body: queryArg.createTeamCommand }), + invalidatesTags: ['teams'], + }), + searchTeams: build.query({ + query: (queryArg) => ({ + url: `/teams/search`, + params: { + page: queryArg.page, + perpage: queryArg.perpage, + name: queryArg.name, + query: queryArg.query, + }, + }), + providesTags: ['teams'], + }), + removeTeamGroupApiQuery: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/groups`, + method: 'DELETE', + params: { + groupId: queryArg.groupId, + }, + }), + invalidatesTags: ['sync_team_groups', 'enterprise'], + }), + getTeamGroupsApi: build.query({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}/groups` }), + providesTags: ['sync_team_groups', 'enterprise'], + }), + addTeamGroupApi: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/groups`, + method: 'POST', + body: queryArg.teamGroupMapping, + }), + invalidatesTags: ['sync_team_groups', 'enterprise'], + }), + searchTeamGroups: build.query({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/groups/search`, + params: { + page: queryArg.page, + perpage: queryArg.perpage, + query: queryArg.query, + name: queryArg.name, + }, + }), + providesTags: ['sync_team_groups', 'enterprise'], + }), + deleteTeamById: build.mutation({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}`, method: 'DELETE' }), + invalidatesTags: ['teams'], + }), + getTeamById: build.query({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}` }), + providesTags: ['teams'], + }), + updateTeam: build.mutation({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}`, method: 'PUT', body: queryArg.updateTeamCommand }), + invalidatesTags: ['teams'], + }), + getTeamMembers: build.query({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}/members` }), + providesTags: ['teams'], + }), + addTeamMember: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/members`, + method: 'POST', + body: queryArg.addTeamMemberCommand, + }), + invalidatesTags: ['teams'], + }), + setTeamMemberships: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/members`, + method: 'PUT', + body: queryArg.setTeamMembershipsCommand, + }), + invalidatesTags: ['teams'], + }), + removeTeamMember: build.mutation({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}/members/${queryArg.userId}`, method: 'DELETE' }), + invalidatesTags: ['teams'], + }), + updateTeamMember: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/members/${queryArg.userId}`, + method: 'PUT', + body: queryArg.updateTeamMemberCommand, + }), + invalidatesTags: ['teams'], + }), + getTeamPreferences: build.query({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}/preferences` }), + providesTags: ['teams', 'preferences'], + }), + updateTeamPreferences: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/preferences`, + method: 'PUT', + body: queryArg.updatePrefsCmd, + }), + invalidatesTags: ['teams', 'preferences'], + }), + getSignedInUser: build.query({ + query: () => ({ url: `/user` }), + providesTags: ['signed_in_user'], + }), + updateSignedInUser: build.mutation({ + query: (queryArg) => ({ url: `/user`, method: 'PUT', body: queryArg.updateUserCommand }), + invalidatesTags: ['signed_in_user'], + }), + getUserAuthTokens: build.query({ + query: () => ({ url: `/user/auth-tokens` }), + providesTags: ['signed_in_user'], + }), + updateUserEmail: build.query({ + query: () => ({ url: `/user/email/update` }), + providesTags: ['user'], + }), + clearHelpFlags: build.query({ + query: () => ({ url: `/user/helpflags/clear` }), + providesTags: ['signed_in_user'], + }), + setHelpFlag: build.mutation({ + query: (queryArg) => ({ url: `/user/helpflags/${queryArg.flagId}`, method: 'PUT' }), + invalidatesTags: ['signed_in_user'], + }), + getSignedInUserOrgList: build.query({ + query: () => ({ url: `/user/orgs` }), + providesTags: ['signed_in_user'], + }), + changeUserPassword: build.mutation({ + query: (queryArg) => ({ url: `/user/password`, method: 'PUT', body: queryArg.changeUserPasswordCommand }), + invalidatesTags: ['signed_in_user'], + }), + getUserPreferences: build.query({ + query: () => ({ url: `/user/preferences` }), + providesTags: ['signed_in_user', 'preferences'], + }), + patchUserPreferences: build.mutation({ + query: (queryArg) => ({ url: `/user/preferences`, method: 'PATCH', body: queryArg.patchPrefsCmd }), + invalidatesTags: ['signed_in_user', 'preferences'], + }), + updateUserPreferences: build.mutation({ + query: (queryArg) => ({ url: `/user/preferences`, method: 'PUT', body: queryArg.updatePrefsCmd }), + invalidatesTags: ['signed_in_user', 'preferences'], + }), + getUserQuotas: build.query({ + query: () => ({ url: `/user/quotas` }), + providesTags: ['quota', 'signed_in_user'], + }), + revokeUserAuthToken: build.mutation({ + query: (queryArg) => ({ url: `/user/revoke-auth-token`, method: 'POST', body: queryArg.revokeAuthTokenCmd }), + invalidatesTags: ['signed_in_user'], + }), + unstarDashboardByUid: build.mutation({ + query: (queryArg) => ({ url: `/user/stars/dashboard/uid/${queryArg.dashboardUid}`, method: 'DELETE' }), + invalidatesTags: ['signed_in_user'], + }), + starDashboardByUid: build.mutation({ + query: (queryArg) => ({ url: `/user/stars/dashboard/uid/${queryArg.dashboardUid}`, method: 'POST' }), + invalidatesTags: ['signed_in_user'], + }), + getSignedInUserTeamList: build.query({ + query: () => ({ url: `/user/teams` }), + providesTags: ['signed_in_user'], + }), + userSetUsingOrg: build.mutation({ + query: (queryArg) => ({ url: `/user/using/${queryArg.orgId}`, method: 'POST' }), + invalidatesTags: ['signed_in_user'], + }), + searchUsers: build.query({ + query: (queryArg) => ({ + url: `/users`, + params: { + perpage: queryArg.perpage, + page: queryArg.page, + }, + }), + providesTags: ['users'], + }), + getUserByLoginOrEmail: build.query({ + query: (queryArg) => ({ + url: `/users/lookup`, + params: { + loginOrEmail: queryArg.loginOrEmail, + }, + }), + providesTags: ['users'], + }), + searchUsersWithPaging: build.query({ + query: () => ({ url: `/users/search` }), + providesTags: ['users'], + }), + getUserById: build.query({ + query: (queryArg) => ({ url: `/users/${queryArg.userId}` }), + providesTags: ['users'], + }), + updateUser: build.mutation({ + query: (queryArg) => ({ url: `/users/${queryArg.userId}`, method: 'PUT', body: queryArg.updateUserCommand }), + invalidatesTags: ['users'], + }), + getUserOrgList: build.query({ + query: (queryArg) => ({ url: `/users/${queryArg.userId}/orgs` }), + providesTags: ['users'], + }), + getUserTeams: build.query({ + query: (queryArg) => ({ url: `/users/${queryArg.userId}/teams` }), + providesTags: ['users'], + }), + routeGetAlertRules: build.query({ + query: () => ({ url: `/v1/provisioning/alert-rules` }), + providesTags: ['provisioning'], + }), + routePostAlertRule: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/alert-rules`, + method: 'POST', + body: queryArg.provisionedAlertRule, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetAlertRulesExport: build.query({ + query: (queryArg) => ({ + url: `/v1/provisioning/alert-rules/export`, + params: { + download: queryArg.download, + format: queryArg.format, + folderUid: queryArg.folderUid, + group: queryArg.group, + ruleUid: queryArg.ruleUid, + }, + }), + providesTags: ['provisioning'], + }), + routeDeleteAlertRule: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/alert-rules/${queryArg.uid}`, + method: 'DELETE', + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetAlertRule: build.query({ + query: (queryArg) => ({ url: `/v1/provisioning/alert-rules/${queryArg.uid}` }), + providesTags: ['provisioning'], + }), + routePutAlertRule: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/alert-rules/${queryArg.uid}`, + method: 'PUT', + body: queryArg.provisionedAlertRule, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetAlertRuleExport: build.query({ + query: (queryArg) => ({ + url: `/v1/provisioning/alert-rules/${queryArg.uid}/export`, + params: { + download: queryArg.download, + format: queryArg.format, + }, + }), + providesTags: ['provisioning'], + }), + routeGetContactpoints: build.query({ + query: (queryArg) => ({ + url: `/v1/provisioning/contact-points`, + params: { + name: queryArg.name, + }, + }), + providesTags: ['provisioning'], + }), + routePostContactpoints: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/contact-points`, + method: 'POST', + body: queryArg.embeddedContactPoint, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetContactpointsExport: build.query< + RouteGetContactpointsExportApiResponse, + RouteGetContactpointsExportApiArg + >({ + query: (queryArg) => ({ + url: `/v1/provisioning/contact-points/export`, + params: { + download: queryArg.download, + format: queryArg.format, + decrypt: queryArg.decrypt, + name: queryArg.name, + }, + }), + providesTags: ['provisioning'], + }), + routeDeleteContactpoints: build.mutation({ + query: (queryArg) => ({ url: `/v1/provisioning/contact-points/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['provisioning'], + }), + routePutContactpoint: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/contact-points/${queryArg.uid}`, + method: 'PUT', + body: queryArg.embeddedContactPoint, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeDeleteAlertRuleGroup: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/folder/${queryArg.folderUid}/rule-groups/${queryArg.group}`, + method: 'DELETE', + }), + invalidatesTags: ['provisioning'], + }), + routeGetAlertRuleGroup: build.query({ + query: (queryArg) => ({ url: `/v1/provisioning/folder/${queryArg.folderUid}/rule-groups/${queryArg.group}` }), + providesTags: ['provisioning'], + }), + routePutAlertRuleGroup: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/folder/${queryArg.folderUid}/rule-groups/${queryArg.group}`, + method: 'PUT', + body: queryArg.alertRuleGroup, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetAlertRuleGroupExport: build.query< + RouteGetAlertRuleGroupExportApiResponse, + RouteGetAlertRuleGroupExportApiArg + >({ + query: (queryArg) => ({ + url: `/v1/provisioning/folder/${queryArg.folderUid}/rule-groups/${queryArg.group}/export`, + params: { + download: queryArg.download, + format: queryArg.format, + }, + }), + providesTags: ['provisioning'], + }), + routeGetMuteTimings: build.query({ + query: () => ({ url: `/v1/provisioning/mute-timings` }), + providesTags: ['provisioning'], + }), + routePostMuteTiming: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/mute-timings`, + method: 'POST', + body: queryArg.muteTimeInterval, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeExportMuteTimings: build.query({ + query: (queryArg) => ({ + url: `/v1/provisioning/mute-timings/export`, + params: { + download: queryArg.download, + format: queryArg.format, + }, + }), + providesTags: ['provisioning'], + }), + routeDeleteMuteTiming: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/mute-timings/${queryArg.name}`, + method: 'DELETE', + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + params: { + version: queryArg.version, + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetMuteTiming: build.query({ + query: (queryArg) => ({ url: `/v1/provisioning/mute-timings/${queryArg.name}` }), + providesTags: ['provisioning'], + }), + routePutMuteTiming: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/mute-timings/${queryArg.name}`, + method: 'PUT', + body: queryArg.muteTimeInterval, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeExportMuteTiming: build.query({ + query: (queryArg) => ({ + url: `/v1/provisioning/mute-timings/${queryArg.name}/export`, + params: { + download: queryArg.download, + format: queryArg.format, + }, + }), + providesTags: ['provisioning'], + }), + routeResetPolicyTree: build.mutation({ + query: () => ({ url: `/v1/provisioning/policies`, method: 'DELETE' }), + invalidatesTags: ['provisioning'], + }), + routeGetPolicyTree: build.query({ + query: () => ({ url: `/v1/provisioning/policies` }), + providesTags: ['provisioning'], + }), + routePutPolicyTree: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/policies`, + method: 'PUT', + body: queryArg.route, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetPolicyTreeExport: build.query({ + query: () => ({ url: `/v1/provisioning/policies/export` }), + providesTags: ['provisioning'], + }), + routeGetTemplates: build.query({ + query: () => ({ url: `/v1/provisioning/templates` }), + providesTags: ['provisioning'], + }), + routeDeleteTemplate: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/templates/${queryArg.name}`, + method: 'DELETE', + params: { + version: queryArg.version, + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetTemplate: build.query({ + query: (queryArg) => ({ url: `/v1/provisioning/templates/${queryArg.name}` }), + providesTags: ['provisioning'], + }), + routePutTemplate: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/templates/${queryArg.name}`, + method: 'PUT', + body: queryArg.notificationTemplateContent, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + listAllProvidersSettings: build.query({ + query: () => ({ url: `/v1/sso-settings` }), + providesTags: ['sso_settings'], + }), + removeProviderSettings: build.mutation({ + query: (queryArg) => ({ url: `/v1/sso-settings/${queryArg.key}`, method: 'DELETE' }), + invalidatesTags: ['sso_settings'], + }), + getProviderSettings: build.query({ + query: (queryArg) => ({ url: `/v1/sso-settings/${queryArg.key}` }), + providesTags: ['sso_settings'], + }), + updateProviderSettings: build.mutation({ + query: (queryArg) => ({ url: `/v1/sso-settings/${queryArg.key}`, method: 'PUT', body: queryArg.body }), + invalidatesTags: ['sso_settings'], + }), + }), + overrideExisting: false, + }); +export { injectedRtkApi as generatedAPI }; +export type SearchResultApiResponse = /** status 200 (empty) */ SearchResult; +export type SearchResultApiArg = void; +export type ListRolesApiResponse = /** status 200 (empty) */ RoleDto[]; +export type ListRolesApiArg = { + delegatable?: boolean; + includeHidden?: boolean; +}; +export type CreateRoleApiResponse = /** status 201 (empty) */ RoleDto; +export type CreateRoleApiArg = { + createRoleForm: CreateRoleForm; +}; +export type DeleteRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteRoleApiArg = { + force?: boolean; + global?: boolean; + roleUid: string; +}; +export type GetRoleApiResponse = /** status 200 (empty) */ RoleDto; +export type GetRoleApiArg = { + roleUid: string; +}; +export type UpdateRoleApiResponse = /** status 200 (empty) */ RoleDto; +export type UpdateRoleApiArg = { + roleUid: string; + updateRoleCommand: UpdateRoleCommand; +}; +export type GetRoleAssignmentsApiResponse = /** status 200 (empty) */ RoleAssignmentsDto; +export type GetRoleAssignmentsApiArg = { + roleUid: string; +}; +export type SetRoleAssignmentsApiResponse = /** status 200 (empty) */ RoleAssignmentsDto; +export type SetRoleAssignmentsApiArg = { + roleUid: string; + setRoleAssignmentsCommand: SetRoleAssignmentsCommand; +}; +export type GetAccessControlStatusApiResponse = /** status 200 (empty) */ Status; +export type GetAccessControlStatusApiArg = void; +export type ListTeamsRolesApiResponse = /** status 200 (empty) */ { + [key: string]: RoleDto[]; +}; +export type ListTeamsRolesApiArg = { + rolesSearchQuery: RolesSearchQuery; +}; +export type ListTeamRolesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type ListTeamRolesApiArg = { + teamId: number; +}; +export type AddTeamRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddTeamRoleApiArg = { + teamId: number; + addTeamRoleCommand: AddTeamRoleCommand; +}; +export type SetTeamRolesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetTeamRolesApiArg = { + teamId: number; +}; +export type RemoveTeamRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveTeamRoleApiArg = { + roleUid: string; + teamId: number; +}; +export type ListUsersRolesApiResponse = /** status 200 (empty) */ { + [key: string]: RoleDto[]; +}; +export type ListUsersRolesApiArg = { + rolesSearchQuery: RolesSearchQuery; +}; +export type ListUserRolesApiResponse = /** status 200 (empty) */ RoleDto[]; +export type ListUserRolesApiArg = { + userId: number; +}; +export type AddUserRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddUserRoleApiArg = { + userId: number; + addUserRoleCommand: AddUserRoleCommand; +}; +export type SetUserRolesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetUserRolesApiArg = { + userId: number; + setUserRolesCommand: SetUserRolesCommand; +}; +export type RemoveUserRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveUserRoleApiArg = { + /** A flag indicating if the assignment is global or not. If set to false, the default org ID of the authenticated user will be used from the request to remove assignment. */ + global?: boolean; + roleUid: string; + userId: number; +}; +export type GetResourceDescriptionApiResponse = /** status 200 (empty) */ Description; +export type GetResourceDescriptionApiArg = { + resource: string; +}; +export type GetResourcePermissionsApiResponse = /** status 200 (empty) */ ResourcePermissionDto[]; +export type GetResourcePermissionsApiArg = { + resource: string; + resourceId: string; +}; +export type SetResourcePermissionsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetResourcePermissionsApiArg = { + resource: string; + resourceId: string; + setPermissionsCommand: SetPermissionsCommand; +}; +export type SetResourcePermissionsForBuiltInRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetResourcePermissionsForBuiltInRoleApiArg = { + resource: string; + resourceId: string; + builtInRole: string; + setPermissionCommand: SetPermissionCommand; +}; +export type SetResourcePermissionsForTeamApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetResourcePermissionsForTeamApiArg = { + resource: string; + resourceId: string; + teamId: number; + setPermissionCommand: SetPermissionCommand; +}; +export type SetResourcePermissionsForUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetResourcePermissionsForUserApiArg = { + resource: string; + resourceId: string; + userId: number; + setPermissionCommand: SetPermissionCommand; +}; +export type GetSyncStatusApiResponse = /** status 200 (empty) */ ActiveSyncStatusDto; +export type GetSyncStatusApiArg = void; +export type ReloadLdapCfgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type ReloadLdapCfgApiArg = void; +export type GetLdapStatusApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type GetLdapStatusApiArg = void; +export type PostSyncUserWithLdapApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type PostSyncUserWithLdapApiArg = { + userId: number; +}; +export type GetUserFromLdapApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type GetUserFromLdapApiArg = { + userName: string; +}; +export type AdminProvisioningReloadAccessControlApiResponse = /** status 202 AcceptedResponse */ ErrorResponseBody; +export type AdminProvisioningReloadAccessControlApiArg = void; +export type AdminProvisioningReloadDashboardsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminProvisioningReloadDashboardsApiArg = void; +export type AdminProvisioningReloadDatasourcesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminProvisioningReloadDatasourcesApiArg = void; +export type AdminProvisioningReloadPluginsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminProvisioningReloadPluginsApiArg = void; +export type AdminGetSettingsApiResponse = /** status 200 (empty) */ SettingsBag; +export type AdminGetSettingsApiArg = void; +export type AdminGetStatsApiResponse = /** status 200 (empty) */ AdminStats; +export type AdminGetStatsApiArg = void; +export type AdminCreateUserApiResponse = /** status 200 (empty) */ AdminCreateUserResponse; +export type AdminCreateUserApiArg = { + adminCreateUserForm: AdminCreateUserForm; +}; +export type AdminDeleteUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminDeleteUserApiArg = { + userId: number; +}; +export type AdminGetUserAuthTokensApiResponse = /** status 200 (empty) */ UserToken[]; +export type AdminGetUserAuthTokensApiArg = { + userId: number; +}; +export type AdminDisableUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminDisableUserApiArg = { + userId: number; +}; +export type AdminEnableUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminEnableUserApiArg = { + userId: number; +}; +export type AdminLogoutUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminLogoutUserApiArg = { + userId: number; +}; +export type AdminUpdateUserPasswordApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminUpdateUserPasswordApiArg = { + userId: number; + adminUpdateUserPasswordForm: AdminUpdateUserPasswordForm; +}; +export type AdminUpdateUserPermissionsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminUpdateUserPermissionsApiArg = { + userId: number; + adminUpdateUserPermissionsForm: AdminUpdateUserPermissionsForm; +}; +export type GetUserQuotaApiResponse = /** status 200 (empty) */ QuotaDto[]; +export type GetUserQuotaApiArg = { + userId: number; +}; +export type UpdateUserQuotaApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateUserQuotaApiArg = { + quotaTarget: string; + userId: number; + updateQuotaCmd: UpdateQuotaCmd; +}; +export type AdminRevokeUserAuthTokenApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminRevokeUserAuthTokenApiArg = { + userId: number; + revokeAuthTokenCmd: RevokeAuthTokenCmd; +}; +export type GetAnnotationsApiResponse = /** status 200 (empty) */ Annotation[]; +export type GetAnnotationsApiArg = { + /** Find annotations created after specific epoch datetime in milliseconds. */ + from?: number; + /** Find annotations created before specific epoch datetime in milliseconds. */ + to?: number; + /** Limit response to annotations created by specific user. */ + userId?: number; + /** Find annotations for a specified alert rule by its ID. + deprecated: AlertID is deprecated and will be removed in future versions. Please use AlertUID instead. */ + alertId?: number; + /** Find annotations for a specified alert rule by its UID. */ + alertUid?: string; + /** Find annotations that are scoped to a specific dashboard */ + dashboardId?: number; + /** Find annotations that are scoped to a specific dashboard */ + dashboardUid?: string; + /** Find annotations that are scoped to a specific panel */ + panelId?: number; + /** Max limit for results returned. */ + limit?: number; + /** Use this to filter organization annotations. Organization annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel. You can filter by multiple tags. */ + tags?: string[]; + /** Return alerts or user created annotations */ + type?: 'alert' | 'annotation'; + /** Match any or all tags */ + matchAny?: boolean; +}; +export type PostAnnotationApiResponse = /** status 200 (empty) */ { + /** ID Identifier of the created annotation. */ + id: number; + /** Message Message of the created annotation. */ + message: string; +}; +export type PostAnnotationApiArg = { + postAnnotationsCmd: PostAnnotationsCmd; +}; +export type PostGraphiteAnnotationApiResponse = /** status 200 (empty) */ { + /** ID Identifier of the created annotation. */ + id: number; + /** Message Message of the created annotation. */ + message: string; +}; +export type PostGraphiteAnnotationApiArg = { + postGraphiteAnnotationsCmd: PostGraphiteAnnotationsCmd; +}; +export type MassDeleteAnnotationsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type MassDeleteAnnotationsApiArg = { + massDeleteAnnotationsCmd: MassDeleteAnnotationsCmd; +}; +export type GetAnnotationTagsApiResponse = + /** status 200 (empty) */ GetAnnotationTagsResponseIsAResponseStructForFindTagsResult; +export type GetAnnotationTagsApiArg = { + /** Tag is a string that you can use to filter tags. */ + tag?: string; + /** Max limit for results returned. */ + limit?: string; +}; +export type DeleteAnnotationByIdApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteAnnotationByIdApiArg = { + annotationId: string; +}; +export type GetAnnotationByIdApiResponse = /** status 200 (empty) */ Annotation; +export type GetAnnotationByIdApiArg = { + annotationId: string; +}; +export type PatchAnnotationApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type PatchAnnotationApiArg = { + annotationId: string; + patchAnnotationsCmd: PatchAnnotationsCmd; +}; +export type UpdateAnnotationApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateAnnotationApiArg = { + annotationId: string; + updateAnnotationsCmd: UpdateAnnotationsCmd; +}; +export type ListDevicesApiResponse = /** status 200 (empty) */ DeviceDto[]; +export type ListDevicesApiArg = void; +export type SearchDevicesApiResponse = /** status 200 (empty) */ SearchDeviceQueryResult; +export type SearchDevicesApiArg = void; +export type GetSessionListApiResponse = /** status 200 (empty) */ CloudMigrationSessionListResponseDto; +export type GetSessionListApiArg = void; +export type CreateSessionApiResponse = /** status 200 (empty) */ CloudMigrationSessionResponseDto; +export type CreateSessionApiArg = { + cloudMigrationSessionRequestDto: CloudMigrationSessionRequestDto; +}; +export type DeleteSessionApiResponse = unknown; +export type DeleteSessionApiArg = { + /** UID of a migration session */ + uid: string; +}; +export type GetSessionApiResponse = /** status 200 (empty) */ CloudMigrationSessionResponseDto; +export type GetSessionApiArg = { + /** UID of a migration session */ + uid: string; +}; +export type CreateSnapshotApiResponse = /** status 200 (empty) */ CreateSnapshotResponseDto; +export type CreateSnapshotApiArg = { + /** UID of a session */ + uid: string; + createSnapshotRequestDto: CreateSnapshotRequestDto; +}; +export type GetSnapshotApiResponse = /** status 200 (empty) */ GetSnapshotResponseDto; +export type GetSnapshotApiArg = { + /** ResultPage is used for pagination with ResultLimit */ + resultPage?: number; + /** Max limit for snapshot results returned. */ + resultLimit?: number; + /** ResultSortColumn can be used to override the default system sort. Valid values are "name", "resource_type", and "status". */ + resultSortColumn?: string; + /** ResultSortOrder is used with ResultSortColumn. Valid values are ASC and DESC. */ + resultSortOrder?: string; + /** ErrorsOnly is used to only return resources with error statuses */ + errorsOnly?: boolean; + /** Session UID of a session */ + uid: string; + /** UID of a snapshot */ + snapshotUid: string; +}; +export type CancelSnapshotApiResponse = unknown; +export type CancelSnapshotApiArg = { + /** Session UID of a session */ + uid: string; + /** UID of a snapshot */ + snapshotUid: string; +}; +export type UploadSnapshotApiResponse = unknown; +export type UploadSnapshotApiArg = { + /** Session UID of a session */ + uid: string; + /** UID of a snapshot */ + snapshotUid: string; +}; +export type GetShapshotListApiResponse = /** status 200 (empty) */ SnapshotListResponseDto; +export type GetShapshotListApiArg = { + /** Page is used for pagination with limit */ + page?: number; + /** Max limit for results returned. */ + limit?: number; + /** Session UID of a session */ + uid: string; + /** Sort with value latest to return results sorted in descending order. */ + sort?: string; +}; +export type GetResourceDependenciesApiResponse = /** status 200 (empty) */ ResourceDependenciesResponseDto; +export type GetResourceDependenciesApiArg = void; +export type GetCloudMigrationTokenApiResponse = /** status 200 (empty) */ GetAccessTokenResponseDto; +export type GetCloudMigrationTokenApiArg = void; +export type CreateCloudMigrationTokenApiResponse = /** status 200 (empty) */ CreateAccessTokenResponseDto; +export type CreateCloudMigrationTokenApiArg = void; +export type DeleteCloudMigrationTokenApiResponse = unknown; +export type DeleteCloudMigrationTokenApiArg = { + /** UID of a cloud migration token */ + uid: string; +}; +export type RouteConvertPrometheusCortexGetRulesApiResponse = unknown; +export type RouteConvertPrometheusCortexGetRulesApiArg = void; +export type RouteConvertPrometheusCortexPostRuleGroupsApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusCortexPostRuleGroupsApiArg = void; +export type RouteConvertPrometheusCortexDeleteNamespaceApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusCortexDeleteNamespaceApiArg = { + namespaceTitle: string; +}; +export type RouteConvertPrometheusCortexGetNamespaceApiResponse = unknown; +export type RouteConvertPrometheusCortexGetNamespaceApiArg = { + namespaceTitle: string; +}; +export type RouteConvertPrometheusCortexPostRuleGroupApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusCortexPostRuleGroupApiArg = { + namespaceTitle: string; + 'x-grafana-alerting-datasource-uid'?: string; + 'x-grafana-alerting-recording-rules-paused'?: boolean; + 'x-grafana-alerting-alert-rules-paused'?: boolean; + 'x-grafana-alerting-target-datasource-uid'?: string; + 'x-grafana-alerting-folder-uid'?: string; + 'x-grafana-alerting-notification-receiver'?: string; + prometheusRuleGroup: PrometheusRuleGroup; +}; +export type RouteConvertPrometheusCortexDeleteRuleGroupApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusCortexDeleteRuleGroupApiArg = { + namespaceTitle: string; + group: string; +}; +export type RouteConvertPrometheusCortexGetRuleGroupApiResponse = unknown; +export type RouteConvertPrometheusCortexGetRuleGroupApiArg = { + namespaceTitle: string; + group: string; +}; +export type RouteConvertPrometheusGetRulesApiResponse = unknown; +export type RouteConvertPrometheusGetRulesApiArg = void; +export type RouteConvertPrometheusPostRuleGroupsApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusPostRuleGroupsApiArg = void; +export type RouteConvertPrometheusDeleteNamespaceApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusDeleteNamespaceApiArg = { + namespaceTitle: string; +}; +export type RouteConvertPrometheusGetNamespaceApiResponse = unknown; +export type RouteConvertPrometheusGetNamespaceApiArg = { + namespaceTitle: string; +}; +export type RouteConvertPrometheusPostRuleGroupApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusPostRuleGroupApiArg = { + namespaceTitle: string; + 'x-grafana-alerting-datasource-uid'?: string; + 'x-grafana-alerting-recording-rules-paused'?: boolean; + 'x-grafana-alerting-alert-rules-paused'?: boolean; + 'x-grafana-alerting-target-datasource-uid'?: string; + 'x-grafana-alerting-folder-uid'?: string; + 'x-grafana-alerting-notification-receiver'?: string; + prometheusRuleGroup: PrometheusRuleGroup; +}; +export type RouteConvertPrometheusDeleteRuleGroupApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusDeleteRuleGroupApiArg = { + namespaceTitle: string; + group: string; +}; +export type RouteConvertPrometheusGetRuleGroupApiResponse = unknown; +export type RouteConvertPrometheusGetRuleGroupApiArg = { + namespaceTitle: string; + group: string; +}; +export type SearchDashboardSnapshotsApiResponse = /** status 200 (empty) */ DashboardSnapshotDto[]; +export type SearchDashboardSnapshotsApiArg = { + /** Search Query */ + query?: string; + /** Limit the number of returned results */ + limit?: number; +}; +export type CalculateDashboardDiffApiResponse = /** status 200 (empty) */ number[]; +export type CalculateDashboardDiffApiArg = { + body: { + base?: CalculateDiffTarget; + /** The type of diff to return + Description: + `basic` + `json` */ + diffType?: 'basic' | 'json'; + new?: CalculateDiffTarget; + }; +}; +export type PostDashboardApiResponse = /** status 200 (empty) */ { + /** FolderUID The unique identifier (uid) of the folder the dashboard belongs to. */ + folderUid?: string; + /** ID The unique identifier (id) of the created/updated dashboard. */ + id: number; + /** Status status of the response. */ + status: string; + /** Slug The slug of the dashboard. */ + title: string; + /** UID The unique identifier (uid) of the created/updated dashboard. */ + uid: string; + /** URL The relative URL for accessing the created/updated dashboard. */ + url: string; + /** Version The version of the dashboard. */ + version: number; +}; +export type PostDashboardApiArg = { + saveDashboardCommand: SaveDashboardCommand; +}; +export type GetHomeDashboardApiResponse = /** status 200 (empty) */ GetHomeDashboardResponse; +export type GetHomeDashboardApiArg = void; +export type ImportDashboardApiResponse = + /** status 200 (empty) */ ImportDashboardResponseResponseObjectReturnedWhenImportingADashboard; +export type ImportDashboardApiArg = { + importDashboardRequest: ImportDashboardRequestRequestObjectForImportingADashboard; +}; +export type InterpolateDashboardApiResponse = /** status 200 (empty) */ any; +export type InterpolateDashboardApiArg = void; +export type ListPublicDashboardsApiResponse = /** status 200 (empty) */ PublicDashboardListResponseWithPagination; +export type ListPublicDashboardsApiArg = void; +export type GetDashboardTagsApiResponse = /** status 200 (empty) */ DashboardTagCloudItem[]; +export type GetDashboardTagsApiArg = void; +export type GetPublicDashboardApiResponse = /** status 200 (empty) */ PublicDashboard; +export type GetPublicDashboardApiArg = { + dashboardUid: string; +}; +export type CreatePublicDashboardApiResponse = /** status 200 (empty) */ PublicDashboard; +export type CreatePublicDashboardApiArg = { + dashboardUid: string; + publicDashboardDto: PublicDashboardDto; +}; +export type DeletePublicDashboardApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeletePublicDashboardApiArg = { + dashboardUid: string; + uid: string; +}; +export type UpdatePublicDashboardApiResponse = /** status 200 (empty) */ PublicDashboard; +export type UpdatePublicDashboardApiArg = { + dashboardUid: string; + uid: string; + publicDashboardDto: PublicDashboardDto; +}; +export type DeleteDashboardByUidApiResponse = /** status 200 (empty) */ { + /** Message Message of the deleted dashboard. */ + message: string; + /** Title Title of the deleted dashboard. */ + title: string; + /** UID Identifier of the deleted dashboard. */ + uid: string; +}; +export type DeleteDashboardByUidApiArg = { + uid: string; +}; +export type GetDashboardByUidApiResponse = /** status 200 (empty) */ DashboardFullWithMeta; +export type GetDashboardByUidApiArg = { + uid: string; +}; +export type GetDashboardPermissionsListByUidApiResponse = /** status 200 (empty) */ DashboardAclInfoDto[]; +export type GetDashboardPermissionsListByUidApiArg = { + uid: string; +}; +export type UpdateDashboardPermissionsByUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateDashboardPermissionsByUidApiArg = { + uid: string; + updateDashboardAclCommand: UpdateDashboardAclCommand; +}; +export type RestoreDashboardVersionByUidApiResponse = /** status 200 (empty) */ { + /** FolderUID The unique identifier (uid) of the folder the dashboard belongs to. */ + folderUid?: string; + /** ID The unique identifier (id) of the created/updated dashboard. */ + id: number; + /** Status status of the response. */ + status: string; + /** Slug The slug of the dashboard. */ + title: string; + /** UID The unique identifier (uid) of the created/updated dashboard. */ + uid: string; + /** URL The relative URL for accessing the created/updated dashboard. */ + url: string; + /** Version The version of the dashboard. */ + version: number; +}; +export type RestoreDashboardVersionByUidApiArg = { + uid: string; + restoreDashboardVersionCommand: RestoreDashboardVersionCommand; +}; +export type GetDashboardVersionsByUidApiResponse = /** status 200 (empty) */ DashboardVersionResponseMeta; +export type GetDashboardVersionsByUidApiArg = { + uid: string; + /** Maximum number of results to return */ + limit?: number; + /** Version to start from when returning queries */ + start?: number; +}; +export type GetDashboardVersionByUidApiResponse = /** status 200 (empty) */ DashboardVersionMeta; +export type GetDashboardVersionByUidApiArg = { + dashboardVersionId: number; + uid: string; +}; +export type GetDataSourcesApiResponse = /** status 200 (empty) */ DataSourceList; +export type GetDataSourcesApiArg = void; +export type AddDataSourceApiResponse = /** status 200 (empty) */ { + datasource: DataSource; + /** ID Identifier of the new data source. */ + id: number; + /** Message Message of the deleted dashboard. */ + message: string; + /** Name of the new data source. */ + name: string; +}; +export type AddDataSourceApiArg = { + addDataSourceCommand: AddDataSourceCommand; +}; +export type GetCorrelationsApiResponse = /** status 200 (empty) */ Correlation[]; +export type GetCorrelationsApiArg = { + /** Limit the maximum number of correlations to return per page */ + limit?: number; + /** Page index for starting fetching correlations */ + page?: number; + /** Source datasource UID filter to be applied to correlations */ + sourceUid?: string[]; +}; +export type GetDataSourceIdByNameApiResponse = /** status 200 (empty) */ { + /** ID Identifier of the data source. */ + id: number; +}; +export type GetDataSourceIdByNameApiArg = { + name: string; +}; +export type DeleteDataSourceByNameApiResponse = /** status 200 (empty) */ { + /** ID Identifier of the deleted data source. */ + id: number; + /** Message Message of the deleted dashboard. */ + message: string; +}; +export type DeleteDataSourceByNameApiArg = { + name: string; +}; +export type GetDataSourceByNameApiResponse = /** status 200 (empty) */ DataSource; +export type GetDataSourceByNameApiArg = { + name: string; +}; +export type DatasourceProxyDeleteByUiDcallsApiResponse = unknown; +export type DatasourceProxyDeleteByUiDcallsApiArg = { + uid: string; + datasourceProxyRoute: string; +}; +export type DatasourceProxyGetByUiDcallsApiResponse = unknown; +export type DatasourceProxyGetByUiDcallsApiArg = { + datasourceProxyRoute: string; + uid: string; +}; +export type DatasourceProxyPostByUiDcallsApiResponse = unknown; +export type DatasourceProxyPostByUiDcallsApiArg = { + datasourceProxyRoute: string; + uid: string; + body: any; +}; +export type GetCorrelationsBySourceUidApiResponse = /** status 200 (empty) */ Correlation[]; +export type GetCorrelationsBySourceUidApiArg = { + sourceUid: string; +}; +export type CreateCorrelationApiResponse = /** status 200 (empty) */ CreateCorrelationResponseBody; +export type CreateCorrelationApiArg = { + sourceUid: string; + createCorrelationCommand: CreateCorrelationCommand; +}; +export type GetCorrelationApiResponse = /** status 200 (empty) */ Correlation; +export type GetCorrelationApiArg = { + sourceUid: string; + correlationUid: string; +}; +export type UpdateCorrelationApiResponse = /** status 200 (empty) */ UpdateCorrelationResponseBody; +export type UpdateCorrelationApiArg = { + sourceUid: string; + correlationUid: string; + updateCorrelationCommand: UpdateCorrelationCommand; +}; +export type DeleteDataSourceByUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteDataSourceByUidApiArg = { + uid: string; +}; +export type GetDataSourceByUidApiResponse = /** status 200 (empty) */ DataSource; +export type GetDataSourceByUidApiArg = { + uid: string; +}; +export type UpdateDataSourceByUidApiResponse = /** status 200 (empty) */ { + datasource: DataSource; + /** ID Identifier of the new data source. */ + id: number; + /** Message Message of the deleted dashboard. */ + message: string; + /** Name of the new data source. */ + name: string; +}; +export type UpdateDataSourceByUidApiArg = { + uid: string; + updateDataSourceCommand: UpdateDataSourceCommand; +}; +export type DeleteCorrelationApiResponse = /** status 200 (empty) */ DeleteCorrelationResponseBody; +export type DeleteCorrelationApiArg = { + uid: string; + correlationUid: string; +}; +export type CheckDatasourceHealthWithUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type CheckDatasourceHealthWithUidApiArg = { + uid: string; +}; +export type GetTeamLbacRulesApiApiResponse = /** status 200 (empty) */ TeamLbacRules; +export type GetTeamLbacRulesApiApiArg = { + uid: string; +}; +export type UpdateTeamLbacRulesApiApiResponse = /** status 200 (empty) */ { + id?: number; + message?: string; + name?: string; + rules?: TeamLbacRule[]; + uid?: string; +}; +export type UpdateTeamLbacRulesApiApiArg = { + uid: string; + updateTeamLbacCommand: UpdateTeamLbacCommand; +}; +export type CallDatasourceResourceWithUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type CallDatasourceResourceWithUidApiArg = { + datasourceProxyRoute: string; + uid: string; +}; +export type GetDataSourceCacheConfigApiResponse = /** status 200 CacheConfigResponse */ CacheConfigResponse; +export type GetDataSourceCacheConfigApiArg = { + dataSourceUid: string; +}; +export type SetDataSourceCacheConfigApiResponse = /** status 200 CacheConfigResponse */ CacheConfigResponse; +export type SetDataSourceCacheConfigApiArg = { + dataSourceUid: string; + cacheConfigSetter: CacheConfigSetter; +}; +export type CleanDataSourceCacheApiResponse = /** status 200 CacheConfigResponse */ CacheConfigResponse; +export type CleanDataSourceCacheApiArg = { + dataSourceUid: string; +}; +export type DisableDataSourceCacheApiResponse = /** status 200 CacheConfigResponse */ CacheConfigResponse; +export type DisableDataSourceCacheApiArg = { + dataSourceUid: string; +}; +export type EnableDataSourceCacheApiResponse = /** status 200 CacheConfigResponse */ CacheConfigResponse; +export type EnableDataSourceCacheApiArg = { + dataSourceUid: string; +}; +export type QueryMetricsWithExpressionsApiResponse = /** status 200 (empty) */ + | QueryDataResponseContainsTheResultsFromAQueryDataRequest + | /** status 207 (empty) */ QueryDataResponseContainsTheResultsFromAQueryDataRequest; +export type QueryMetricsWithExpressionsApiArg = { + metricRequest: MetricRequest; +}; +export type GetFoldersApiResponse = /** status 200 (empty) */ FolderSearchHit[]; +export type GetFoldersApiArg = { + /** Limit the maximum number of folders to return */ + limit?: number; + /** Page index for starting fetching folders */ + page?: number; + /** The parent folder UID */ + parentUid?: string; + /** Set to `Edit` to return folders that the user can edit */ + permission?: 'Edit' | 'View'; +}; +export type CreateFolderApiResponse = /** status 200 (empty) */ Folder; +export type CreateFolderApiArg = { + createFolderCommand: CreateFolderCommand; +}; +export type DeleteFolderApiResponse = /** status 200 (empty) */ { + /** ID Identifier of the deleted folder. */ + id: number; + /** Message Message of the deleted folder. */ + message: string; + /** Title of the deleted folder. */ + title: string; +}; +export type DeleteFolderApiArg = { + folderUid: string; + /** If `true` any Grafana 8 Alerts under this folder will be deleted. + Set to `false` so that the request will fail if the folder contains any Grafana 8 Alerts. */ + forceDeleteRules?: boolean; +}; +export type GetFolderByUidApiResponse = /** status 200 (empty) */ Folder; +export type GetFolderByUidApiArg = { + folderUid: string; +}; +export type UpdateFolderApiResponse = /** status 200 (empty) */ Folder; +export type UpdateFolderApiArg = { + folderUid: string; + /** To change the unique identifier (uid), provide another one. + To overwrite an existing folder with newer version, set `overwrite` to `true`. + Provide the current version to safelly update the folder: if the provided version differs from the stored one the request will fail, unless `overwrite` is `true`. */ + updateFolderCommand: UpdateFolderCommand; +}; +export type GetFolderDescendantCountsApiResponse = /** status 200 (empty) */ DescendantCounts; +export type GetFolderDescendantCountsApiArg = { + folderUid: string; +}; +export type MoveFolderApiResponse = /** status 200 (empty) */ Folder; +export type MoveFolderApiArg = { + folderUid: string; + moveFolderCommand: MoveFolderCommand; +}; +export type GetFolderPermissionListApiResponse = /** status 200 (empty) */ DashboardAclInfoDto[]; +export type GetFolderPermissionListApiArg = { + folderUid: string; +}; +export type UpdateFolderPermissionsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateFolderPermissionsApiArg = { + folderUid: string; + updateDashboardAclCommand: UpdateDashboardAclCommand; +}; +export type GetMappedGroupsApiResponse = /** status 200 (empty) */ GetGroupsResponse; +export type GetMappedGroupsApiArg = void; +export type DeleteGroupMappingsApiResponse = + /** status 204 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteGroupMappingsApiArg = { + groupId: string; +}; +export type CreateGroupMappingsApiResponse = /** status 201 (empty) */ MessageResponse; +export type CreateGroupMappingsApiArg = { + groupId: string; + groupAttributes: GroupAttributes; +}; +export type UpdateGroupMappingsApiResponse = /** status 201 (empty) */ MessageResponse; +export type UpdateGroupMappingsApiArg = { + groupId: string; + groupAttributes: GroupAttributes; +}; +export type GetGroupRolesApiResponse = /** status 200 (empty) */ RoleDto[]; +export type GetGroupRolesApiArg = { + groupId: string; +}; +export type GetHealthApiResponse = /** status 200 healthResponse */ HealthResponse; +export type GetHealthApiArg = void; +export type GetLibraryElementsApiResponse = + /** status 200 (empty) */ LibraryElementSearchResponseIsAResponseStructForLibraryElementSearchResult; +export type GetLibraryElementsApiArg = { + /** Part of the name or description searched for. */ + searchString?: string; + /** Kind of element to search for. */ + kind?: 1; + /** Sort order of elements. */ + sortDirection?: 'alpha-asc' | 'alpha-desc'; + /** A comma separated list of types to filter the elements by */ + typeFilter?: string; + /** Element UID to exclude from search results. */ + excludeUid?: string; + /** A comma separated list of folder ID(s) to filter the elements by. */ + folderFilter?: string; + /** The number of results per page. */ + perPage?: number; + /** The page for a set of records, given that only perPage records are returned at a time. Numbering starts at 1. */ + page?: number; +}; +export type CreateLibraryElementApiResponse = + /** status 200 (empty) */ LibraryElementResponseIsAResponseStructForLibraryElementDto; +export type CreateLibraryElementApiArg = { + createLibraryElementCommand: CreateLibraryElementCommand; +}; +export type GetLibraryElementByNameApiResponse = + /** status 200 (empty) */ LibraryElementArrayResponseIsAResponseStructForAnArrayOfLibraryElementDto; +export type GetLibraryElementByNameApiArg = { + libraryElementName: string; +}; +export type DeleteLibraryElementByUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteLibraryElementByUidApiArg = { + libraryElementUid: string; +}; +export type GetLibraryElementByUidApiResponse = + /** status 200 (empty) */ LibraryElementResponseIsAResponseStructForLibraryElementDto; +export type GetLibraryElementByUidApiArg = { + libraryElementUid: string; +}; +export type UpdateLibraryElementApiResponse = + /** status 200 (empty) */ LibraryElementResponseIsAResponseStructForLibraryElementDto; +export type UpdateLibraryElementApiArg = { + libraryElementUid: string; + patchLibraryElementCommand: PatchLibraryElementCommand; +}; +export type GetLibraryElementConnectionsApiResponse = + /** status 200 (empty) */ LibraryElementConnectionsResponseIsAResponseStructForAnArrayOfLibraryElementConnectionDto; +export type GetLibraryElementConnectionsApiArg = { + libraryElementUid: string; +}; +export type GetStatusApiResponse = unknown; +export type GetStatusApiArg = void; +export type RefreshLicenseStatsApiResponse = /** status 200 (empty) */ ActiveUserStats; +export type RefreshLicenseStatsApiArg = void; +export type DeleteLicenseTokenApiResponse = /** status 202 AcceptedResponse */ ErrorResponseBody; +export type DeleteLicenseTokenApiArg = { + deleteTokenCommand: DeleteTokenCommand; +}; +export type GetLicenseTokenApiResponse = /** status 200 (empty) */ Token; +export type GetLicenseTokenApiArg = void; +export type PostLicenseTokenApiResponse = /** status 200 (empty) */ Token; +export type PostLicenseTokenApiArg = { + deleteTokenCommand: DeleteTokenCommand; +}; +export type PostRenewLicenseTokenApiResponse = unknown; +export type PostRenewLicenseTokenApiArg = { + body: object; +}; +export type GetSamlLogoutApiResponse = unknown; +export type GetSamlLogoutApiArg = void; +export type GetCurrentOrgApiResponse = /** status 200 (empty) */ OrgDetailsDto; +export type GetCurrentOrgApiArg = void; +export type UpdateCurrentOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateCurrentOrgApiArg = { + updateOrgForm: UpdateOrgForm; +}; +export type UpdateCurrentOrgAddressApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateCurrentOrgAddressApiArg = { + updateOrgAddressForm: UpdateOrgAddressForm; +}; +export type GetPendingOrgInvitesApiResponse = /** status 200 (empty) */ TempUserDto[]; +export type GetPendingOrgInvitesApiArg = void; +export type AddOrgInviteApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddOrgInviteApiArg = { + addInviteForm: AddInviteForm; +}; +export type RevokeInviteApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RevokeInviteApiArg = { + invitationCode: string; +}; +export type GetOrgPreferencesApiResponse = /** status 200 (empty) */ PreferencesSpec; +export type GetOrgPreferencesApiArg = void; +export type PatchOrgPreferencesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type PatchOrgPreferencesApiArg = { + patchPrefsCmd: PatchPrefsCmd; +}; +export type UpdateOrgPreferencesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgPreferencesApiArg = { + updatePrefsCmd: UpdatePrefsCmd; +}; +export type GetCurrentOrgQuotaApiResponse = /** status 200 (empty) */ QuotaDto[]; +export type GetCurrentOrgQuotaApiArg = void; +export type GetOrgUsersForCurrentOrgApiResponse = /** status 200 (empty) */ OrgUserDto[]; +export type GetOrgUsersForCurrentOrgApiArg = { + query?: string; + limit?: number; +}; +export type AddOrgUserToCurrentOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddOrgUserToCurrentOrgApiArg = { + addOrgUserCommand: AddOrgUserCommand; +}; +export type GetOrgUsersForCurrentOrgLookupApiResponse = /** status 200 (empty) */ UserLookupDto[]; +export type GetOrgUsersForCurrentOrgLookupApiArg = { + query?: string; + limit?: number; +}; +export type RemoveOrgUserForCurrentOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveOrgUserForCurrentOrgApiArg = { + userId: number; +}; +export type UpdateOrgUserForCurrentOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgUserForCurrentOrgApiArg = { + userId: number; + updateOrgUserCommand: UpdateOrgUserCommand; +}; +export type SearchOrgsApiResponse = /** status 200 (empty) */ OrgDto[]; +export type SearchOrgsApiArg = { + page?: number; + /** Number of items per page + The totalCount field in the response can be used for pagination list E.g. if totalCount is equal to 100 teams and the perpage parameter is set to 10 then there are 10 pages of teams. */ + perpage?: number; + name?: string; + /** If set it will return results where the query value is contained in the name field. Query values with spaces need to be URL encoded. */ + query?: string; +}; +export type CreateOrgApiResponse = /** status 200 (empty) */ { + /** Message Message of the created org. */ + message: string; + /** ID Identifier of the created org. */ + orgId: number; +}; +export type CreateOrgApiArg = { + createOrgCommand: CreateOrgCommand; +}; +export type GetOrgByNameApiResponse = /** status 200 (empty) */ OrgDetailsDto; +export type GetOrgByNameApiArg = { + orgName: string; +}; +export type DeleteOrgByIdApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteOrgByIdApiArg = { + orgId: number; +}; +export type GetOrgByIdApiResponse = /** status 200 (empty) */ OrgDetailsDto; +export type GetOrgByIdApiArg = { + orgId: number; +}; +export type UpdateOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgApiArg = { + orgId: number; + updateOrgForm: UpdateOrgForm; +}; +export type UpdateOrgAddressApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgAddressApiArg = { + orgId: number; + updateOrgAddressForm: UpdateOrgAddressForm; +}; +export type GetOrgQuotaApiResponse = /** status 200 (empty) */ QuotaDto[]; +export type GetOrgQuotaApiArg = { + orgId: number; +}; +export type UpdateOrgQuotaApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgQuotaApiArg = { + quotaTarget: string; + orgId: number; + updateQuotaCmd: UpdateQuotaCmd; +}; +export type GetOrgUsersApiResponse = /** status 200 (empty) */ OrgUserDto[]; +export type GetOrgUsersApiArg = { + orgId: number; +}; +export type AddOrgUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddOrgUserApiArg = { + orgId: number; + addOrgUserCommand: AddOrgUserCommand; +}; +export type SearchOrgUsersApiResponse = /** status 200 (empty) */ SearchOrgUsersQueryResult; +export type SearchOrgUsersApiArg = { + orgId: number; +}; +export type RemoveOrgUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveOrgUserApiArg = { + orgId: number; + userId: number; +}; +export type UpdateOrgUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgUserApiArg = { + orgId: number; + userId: number; + updateOrgUserCommand: UpdateOrgUserCommand; +}; +export type SearchPlaylistsApiResponse = /** status 200 (empty) */ Playlists; +export type SearchPlaylistsApiArg = { + query?: string; + /** in:limit */ + limit?: number; +}; +export type CreatePlaylistApiResponse = /** status 200 (empty) */ Playlist; +export type CreatePlaylistApiArg = { + createPlaylistCommand: CreatePlaylistCommand; +}; +export type DeletePlaylistApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeletePlaylistApiArg = { + uid: string; +}; +export type GetPlaylistApiResponse = /** status 200 (empty) */ PlaylistDto; +export type GetPlaylistApiArg = { + uid: string; +}; +export type UpdatePlaylistApiResponse = /** status 200 (empty) */ PlaylistDto; +export type UpdatePlaylistApiArg = { + uid: string; + updatePlaylistCommand: UpdatePlaylistCommand; +}; +export type GetPlaylistItemsApiResponse = /** status 200 (empty) */ PlaylistItemDto[]; +export type GetPlaylistItemsApiArg = { + uid: string; +}; +export type ViewPublicDashboardApiResponse = /** status 200 (empty) */ DashboardFullWithMeta; +export type ViewPublicDashboardApiArg = { + accessToken: string; +}; +export type GetPublicAnnotationsApiResponse = /** status 200 (empty) */ AnnotationEvent[]; +export type GetPublicAnnotationsApiArg = { + accessToken: string; +}; +export type QueryPublicDashboardApiResponse = + /** status 200 (empty) */ QueryDataResponseContainsTheResultsFromAQueryDataRequest; +export type QueryPublicDashboardApiArg = { + accessToken: string; + panelId: number; +}; +export type SearchQueriesApiResponse = /** status 200 (empty) */ QueryHistorySearchResponse; +export type SearchQueriesApiArg = { + /** List of data source UIDs to search for */ + datasourceUid?: string[]; + /** Text inside query or comments that is searched for */ + searchString?: string; + /** Flag indicating if only starred queries should be returned */ + onlyStarred?: boolean; + /** Sort method */ + sort?: 'time-desc' | 'time-asc'; + /** Use this parameter to access hits beyond limit. Numbering starts at 1. limit param acts as page size. */ + page?: number; + /** Limit the number of returned results */ + limit?: number; + /** From range for the query history search */ + from?: number; + /** To range for the query history search */ + to?: number; +}; +export type CreateQueryApiResponse = /** status 200 (empty) */ QueryHistoryResponse; +export type CreateQueryApiArg = { + createQueryInQueryHistoryCommand: CreateQueryInQueryHistoryCommand; +}; +export type UnstarQueryApiResponse = /** status 200 (empty) */ QueryHistoryResponse; +export type UnstarQueryApiArg = { + queryHistoryUid: string; +}; +export type StarQueryApiResponse = /** status 200 (empty) */ QueryHistoryResponse; +export type StarQueryApiArg = { + queryHistoryUid: string; +}; +export type DeleteQueryApiResponse = /** status 200 (empty) */ QueryHistoryDeleteQueryResponse; +export type DeleteQueryApiArg = { + queryHistoryUid: string; +}; +export type PatchQueryCommentApiResponse = /** status 200 (empty) */ QueryHistoryResponse; +export type PatchQueryCommentApiArg = { + queryHistoryUid: string; + patchQueryCommentInQueryHistoryCommand: PatchQueryCommentInQueryHistoryCommand; +}; +export type ListRecordingRulesApiResponse = /** status 200 (empty) */ RecordingRuleJson[]; +export type ListRecordingRulesApiArg = void; +export type CreateRecordingRuleApiResponse = /** status 200 (empty) */ RecordingRuleJson; +export type CreateRecordingRuleApiArg = { + recordingRuleJson: RecordingRuleJson; +}; +export type UpdateRecordingRuleApiResponse = /** status 200 (empty) */ RecordingRuleJson; +export type UpdateRecordingRuleApiArg = { + recordingRuleJson: RecordingRuleJson; +}; +export type TestCreateRecordingRuleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type TestCreateRecordingRuleApiArg = { + recordingRuleJson: RecordingRuleJson; +}; +export type DeleteRecordingRuleWriteTargetApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteRecordingRuleWriteTargetApiArg = void; +export type GetRecordingRuleWriteTargetApiResponse = /** status 200 (empty) */ PrometheusRemoteWriteTargetJson; +export type GetRecordingRuleWriteTargetApiArg = void; +export type CreateRecordingRuleWriteTargetApiResponse = /** status 200 (empty) */ PrometheusRemoteWriteTargetJson; +export type CreateRecordingRuleWriteTargetApiArg = { + prometheusRemoteWriteTargetJson: PrometheusRemoteWriteTargetJson; +}; +export type DeleteRecordingRuleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteRecordingRuleApiArg = { + recordingRuleId: number; +}; +export type GetReportsApiResponse = /** status 200 (empty) */ Report[]; +export type GetReportsApiArg = void; +export type CreateReportApiResponse = /** status 200 (empty) */ { + id?: number; + message?: string; +}; +export type CreateReportApiArg = { + createOrUpdateReport: CreateOrUpdateReport; +}; +export type GetReportsByDashboardUidApiResponse = /** status 200 (empty) */ Report[]; +export type GetReportsByDashboardUidApiArg = { + uid: string; +}; +export type SendReportApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SendReportApiArg = { + reportEmail: ReportEmail; +}; +export type GetSettingsImageApiResponse = /** status 200 (empty) */ number[]; +export type GetSettingsImageApiArg = void; +export type RenderReportCsVsApiResponse = /** status 200 (empty) */ number[] | /** status 204 (empty) */ object; +export type RenderReportCsVsApiArg = { + dashboards?: string; + title?: string; +}; +export type RenderReportPdFsApiResponse = /** status 200 (empty) */ number[]; +export type RenderReportPdFsApiArg = { + dashboards?: string; + orientation?: string; + layout?: string; + title?: string; + scaleFactor?: string; + includeTables?: string; +}; +export type GetReportSettingsApiResponse = /** status 200 (empty) */ ReportSettings; +export type GetReportSettingsApiArg = void; +export type SaveReportSettingsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SaveReportSettingsApiArg = { + reportSettings: ReportSettings; +}; +export type SendTestEmailApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SendTestEmailApiArg = { + createOrUpdateReport: CreateOrUpdateReport; +}; +export type PostAcsApiResponse = unknown; +export type PostAcsApiArg = { + relayState?: string; +}; +export type GetMetadataApiResponse = /** status 200 (empty) */ number[]; +export type GetMetadataApiArg = void; +export type GetSloApiResponse = unknown; +export type GetSloApiArg = void; +export type PostSloApiResponse = unknown; +export type PostSloApiArg = { + samlRequest?: string; + samlResponse?: string; +}; +export type SearchApiResponse = /** status 200 (empty) */ HitList; +export type SearchApiArg = { + /** Search Query */ + query?: string; + /** List of tags to search for */ + tag?: string[]; + /** Type to search for, dash-folder or dash-db */ + type?: 'dash-folder' | 'dash-db'; + /** List of dashboard id’s to search for + This is deprecated: users should use the `dashboardUIDs` query parameter instead */ + dashboardIds?: number[]; + /** List of dashboard uid’s to search for */ + dashboardUiDs?: string[]; + /** List of folder id’s to search in for dashboards + If it's `0` then it will query for the top level folders + This is deprecated: users should use the `folderUIDs` query parameter instead */ + folderIds?: number[]; + /** List of folder UID’s to search in for dashboards + If it's an empty string then it will query for the top level folders */ + folderUiDs?: string[]; + /** Flag indicating if only starred Dashboards should be returned */ + starred?: boolean; + /** Limit the number of returned results (max 5000) */ + limit?: number; + /** Use this parameter to access hits beyond limit. Numbering starts at 1. limit param acts as page size. Only available in Grafana v6.2+. */ + page?: number; + /** Set to `Edit` to return dashboards/folders that the user can edit */ + permission?: 'Edit' | 'View'; + /** Sort method; for listing all the possible sort methods use the search sorting endpoint. */ + sort?: 'alpha-asc' | 'alpha-desc'; + /** Flag indicating if only soft deleted Dashboards should be returned */ + deleted?: boolean; +}; +export type ListSortOptionsApiResponse = /** status 200 (empty) */ { + description?: string; + displayName?: string; + meta?: string; + name?: string; +}; +export type ListSortOptionsApiArg = void; +export type CreateServiceAccountApiResponse = /** status 201 (empty) */ ServiceAccountDto; +export type CreateServiceAccountApiArg = { + createServiceAccountForm: CreateServiceAccountForm; +}; +export type SearchOrgServiceAccountsWithPagingApiResponse = /** status 200 (empty) */ SearchOrgServiceAccountsResult; +export type SearchOrgServiceAccountsWithPagingApiArg = { + disabled?: boolean; + expiredTokens?: boolean; + /** It will return results where the query value is contained in one of the name. + Query values with spaces need to be URL encoded. */ + query?: string; + /** The default value is 1000. */ + perpage?: number; + /** The default value is 1. */ + page?: number; +}; +export type DeleteServiceAccountApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteServiceAccountApiArg = { + serviceAccountId: number; +}; +export type RetrieveServiceAccountApiResponse = /** status 200 (empty) */ ServiceAccountDto; +export type RetrieveServiceAccountApiArg = { + serviceAccountId: number; +}; +export type UpdateServiceAccountApiResponse = /** status 200 (empty) */ { + id?: number; + message?: string; + name?: string; + serviceaccount?: ServiceAccountProfileDto; +}; +export type UpdateServiceAccountApiArg = { + serviceAccountId: number; + updateServiceAccountForm: UpdateServiceAccountForm; +}; +export type ListTokensApiResponse = /** status 200 (empty) */ TokenDto[]; +export type ListTokensApiArg = { + serviceAccountId: number; +}; +export type CreateTokenApiResponse = /** status 200 (empty) */ NewApiKeyResult; +export type CreateTokenApiArg = { + serviceAccountId: number; + addServiceAccountTokenCommand: AddServiceAccountTokenCommand; +}; +export type DeleteTokenApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteTokenApiArg = { + tokenId: number; + serviceAccountId: number; +}; +export type RetrieveJwksApiResponse = /** status 200 (empty) */ { + keys?: JsonWebKey[]; +}; +export type RetrieveJwksApiArg = void; +export type GetSharingOptionsApiResponse = /** status 200 (empty) */ { + externalEnabled?: boolean; + externalSnapshotName?: string; + externalSnapshotURL?: string; +}; +export type GetSharingOptionsApiArg = void; +export type CreateDashboardSnapshotApiResponse = /** status 200 (empty) */ { + /** Unique key used to delete the snapshot. It is different from the key so that only the creator can delete the snapshot. */ + deleteKey?: string; + deleteUrl?: string; + /** Snapshot id */ + id?: number; + /** Unique key */ + key?: string; + url?: string; +}; +export type CreateDashboardSnapshotApiArg = { + createDashboardSnapshotCommand: CreateDashboardSnapshotCommand; +}; +export type DeleteDashboardSnapshotByDeleteKeyApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteDashboardSnapshotByDeleteKeyApiArg = { + deleteKey: string; +}; +export type DeleteDashboardSnapshotApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteDashboardSnapshotApiArg = { + key: string; +}; +export type GetDashboardSnapshotApiResponse = unknown; +export type GetDashboardSnapshotApiArg = { + key: string; +}; +export type CreateTeamApiResponse = /** status 200 (empty) */ { + message?: string; + teamId?: number; + uid?: string; +}; +export type CreateTeamApiArg = { + createTeamCommand: CreateTeamCommand; +}; +export type SearchTeamsApiResponse = /** status 200 (empty) */ SearchTeamQueryResult; +export type SearchTeamsApiArg = { + page?: number; + /** Number of items per page + The totalCount field in the response can be used for pagination list E.g. if totalCount is equal to 100 teams and the perpage parameter is set to 10 then there are 10 pages of teams. */ + perpage?: number; + name?: string; + /** If set it will return results where the query value is contained in the name field. Query values with spaces need to be URL encoded. */ + query?: string; +}; +export type RemoveTeamGroupApiQueryApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveTeamGroupApiQueryApiArg = { + groupId?: string; + teamId: number; +}; +export type GetTeamGroupsApiApiResponse = /** status 200 (empty) */ TeamGroupDto[]; +export type GetTeamGroupsApiApiArg = { + teamId: number; +}; +export type AddTeamGroupApiApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddTeamGroupApiApiArg = { + teamId: number; + teamGroupMapping: TeamGroupMapping; +}; +export type SearchTeamGroupsApiResponse = /** status 200 (empty) */ SearchTeamGroupsQueryResult; +export type SearchTeamGroupsApiArg = { + teamId: number; + page?: number; + /** Number of items per page */ + perpage?: number; + /** If set it will return results where the query value is contained in the name field. Query values with spaces need to be URL encoded. */ + query?: string; + /** Filter by exact name match */ + name?: string; +}; +export type DeleteTeamByIdApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteTeamByIdApiArg = { + teamId: string; +}; +export type GetTeamByIdApiResponse = /** status 200 (empty) */ TeamDto; +export type GetTeamByIdApiArg = { + teamId: string; +}; +export type UpdateTeamApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateTeamApiArg = { + teamId: string; + updateTeamCommand: UpdateTeamCommand; +}; +export type GetTeamMembersApiResponse = /** status 200 (empty) */ TeamMemberDto[]; +export type GetTeamMembersApiArg = { + teamId: string; +}; +export type AddTeamMemberApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddTeamMemberApiArg = { + teamId: string; + addTeamMemberCommand: AddTeamMemberCommand; +}; +export type SetTeamMembershipsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetTeamMembershipsApiArg = { + teamId: string; + setTeamMembershipsCommand: SetTeamMembershipsCommand; +}; +export type RemoveTeamMemberApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveTeamMemberApiArg = { + teamId: string; + userId: number; +}; +export type UpdateTeamMemberApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateTeamMemberApiArg = { + teamId: string; + userId: number; + updateTeamMemberCommand: UpdateTeamMemberCommand; +}; +export type GetTeamPreferencesApiResponse = /** status 200 (empty) */ PreferencesSpec; +export type GetTeamPreferencesApiArg = { + teamId: string; +}; +export type UpdateTeamPreferencesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateTeamPreferencesApiArg = { + teamId: string; + updatePrefsCmd: UpdatePrefsCmd; +}; +export type GetSignedInUserApiResponse = /** status 200 (empty) */ UserProfileDto; +export type GetSignedInUserApiArg = void; +export type UpdateSignedInUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateSignedInUserApiArg = { + /** To change the email, name, login, theme, provide another one. */ + updateUserCommand: UpdateUserCommand; +}; +export type GetUserAuthTokensApiResponse = /** status 200 (empty) */ UserToken[]; +export type GetUserAuthTokensApiArg = void; +export type UpdateUserEmailApiResponse = unknown; +export type UpdateUserEmailApiArg = void; +export type ClearHelpFlagsApiResponse = /** status 200 (empty) */ { + helpFlags1?: number; + message?: string; +}; +export type ClearHelpFlagsApiArg = void; +export type SetHelpFlagApiResponse = /** status 200 (empty) */ { + helpFlags1?: number; + message?: string; +}; +export type SetHelpFlagApiArg = { + flagId: string; +}; +export type GetSignedInUserOrgListApiResponse = /** status 200 (empty) */ UserOrgDto[]; +export type GetSignedInUserOrgListApiArg = void; +export type ChangeUserPasswordApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type ChangeUserPasswordApiArg = { + /** To change the email, name, login, theme, provide another one. */ + changeUserPasswordCommand: ChangeUserPasswordCommand; +}; +export type GetUserPreferencesApiResponse = /** status 200 (empty) */ PreferencesSpec; +export type GetUserPreferencesApiArg = void; +export type PatchUserPreferencesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type PatchUserPreferencesApiArg = { + patchPrefsCmd: PatchPrefsCmd; +}; +export type UpdateUserPreferencesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateUserPreferencesApiArg = { + updatePrefsCmd: UpdatePrefsCmd; +}; +export type GetUserQuotasApiResponse = /** status 200 (empty) */ QuotaDto[]; +export type GetUserQuotasApiArg = void; +export type RevokeUserAuthTokenApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RevokeUserAuthTokenApiArg = { + revokeAuthTokenCmd: RevokeAuthTokenCmd; +}; +export type UnstarDashboardByUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UnstarDashboardByUidApiArg = { + dashboardUid: string; +}; +export type StarDashboardByUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type StarDashboardByUidApiArg = { + dashboardUid: string; +}; +export type GetSignedInUserTeamListApiResponse = /** status 200 (empty) */ TeamDto[]; +export type GetSignedInUserTeamListApiArg = void; +export type UserSetUsingOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UserSetUsingOrgApiArg = { + orgId: number; +}; +export type SearchUsersApiResponse = /** status 200 (empty) */ UserSearchHitDto[]; +export type SearchUsersApiArg = { + /** Limit the maximum number of users to return per page */ + perpage?: number; + /** Page index for starting fetching users */ + page?: number; +}; +export type GetUserByLoginOrEmailApiResponse = /** status 200 (empty) */ UserProfileDto; +export type GetUserByLoginOrEmailApiArg = { + /** loginOrEmail of the user */ + loginOrEmail: string; +}; +export type SearchUsersWithPagingApiResponse = /** status 200 (empty) */ SearchUserQueryResult; +export type SearchUsersWithPagingApiArg = void; +export type GetUserByIdApiResponse = /** status 200 (empty) */ UserProfileDto; +export type GetUserByIdApiArg = { + userId: number; +}; +export type UpdateUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateUserApiArg = { + userId: number; + /** To change the email, name, login, theme, provide another one. */ + updateUserCommand: UpdateUserCommand; +}; +export type GetUserOrgListApiResponse = /** status 200 (empty) */ UserOrgDto[]; +export type GetUserOrgListApiArg = { + userId: number; +}; +export type GetUserTeamsApiResponse = /** status 200 (empty) */ TeamDto[]; +export type GetUserTeamsApiArg = { + userId: number; +}; +export type RouteGetAlertRulesApiResponse = /** status 200 ProvisionedAlertRules */ ProvisionedAlertRulesRead; +export type RouteGetAlertRulesApiArg = void; +export type RoutePostAlertRuleApiResponse = /** status 201 ProvisionedAlertRule */ ProvisionedAlertRuleRead; +export type RoutePostAlertRuleApiArg = { + 'X-Disable-Provenance'?: string; + provisionedAlertRule: ProvisionedAlertRule; +}; +export type RouteGetAlertRulesExportApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteGetAlertRulesExportApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; + /** UIDs of folders from which to export rules */ + folderUid?: string[]; + /** Name of group of rules to export. Must be specified only together with a single folder UID */ + group?: string; + /** UID of alert rule to export. If specified, parameters folderUid and group must be empty. */ + ruleUid?: string; +}; +export type RouteDeleteAlertRuleApiResponse = unknown; +export type RouteDeleteAlertRuleApiArg = { + /** Alert rule UID */ + uid: string; + 'X-Disable-Provenance'?: string; +}; +export type RouteGetAlertRuleApiResponse = /** status 200 ProvisionedAlertRule */ ProvisionedAlertRuleRead; +export type RouteGetAlertRuleApiArg = { + /** Alert rule UID */ + uid: string; +}; +export type RoutePutAlertRuleApiResponse = /** status 200 ProvisionedAlertRule */ ProvisionedAlertRuleRead; +export type RoutePutAlertRuleApiArg = { + /** Alert rule UID */ + uid: string; + 'X-Disable-Provenance'?: string; + provisionedAlertRule: ProvisionedAlertRule; +}; +export type RouteGetAlertRuleExportApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteGetAlertRuleExportApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; + /** Alert rule UID */ + uid: string; +}; +export type RouteGetContactpointsApiResponse = /** status 200 ContactPoints */ ContactPointsRead; +export type RouteGetContactpointsApiArg = { + /** Filter by name */ + name?: string; +}; +export type RoutePostContactpointsApiResponse = /** status 202 EmbeddedContactPoint */ EmbeddedContactPointRead; +export type RoutePostContactpointsApiArg = { + 'X-Disable-Provenance'?: string; + embeddedContactPoint: EmbeddedContactPoint; +}; +export type RouteGetContactpointsExportApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteGetContactpointsExportApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; + /** Whether any contained secure settings should be decrypted or left redacted. Redacted settings will contain RedactedValue instead. Currently, only org admin can view decrypted secure settings. */ + decrypt?: boolean; + /** Filter by name */ + name?: string; +}; +export type RouteDeleteContactpointsApiResponse = unknown; +export type RouteDeleteContactpointsApiArg = { + /** UID is the contact point unique identifier */ + uid: string; +}; +export type RoutePutContactpointApiResponse = /** status 202 Ack */ Ack; +export type RoutePutContactpointApiArg = { + /** UID is the contact point unique identifier */ + uid: string; + 'X-Disable-Provenance'?: string; + embeddedContactPoint: EmbeddedContactPoint; +}; +export type RouteDeleteAlertRuleGroupApiResponse = unknown; +export type RouteDeleteAlertRuleGroupApiArg = { + folderUid: string; + group: string; +}; +export type RouteGetAlertRuleGroupApiResponse = /** status 200 AlertRuleGroup */ AlertRuleGroupRead; +export type RouteGetAlertRuleGroupApiArg = { + folderUid: string; + group: string; +}; +export type RoutePutAlertRuleGroupApiResponse = /** status 200 AlertRuleGroup */ AlertRuleGroupRead; +export type RoutePutAlertRuleGroupApiArg = { + 'X-Disable-Provenance'?: string; + folderUid: string; + group: string; + alertRuleGroup: AlertRuleGroup; +}; +export type RouteGetAlertRuleGroupExportApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteGetAlertRuleGroupExportApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; + folderUid: string; + group: string; +}; +export type RouteGetMuteTimingsApiResponse = /** status 200 MuteTimings */ MuteTimings; +export type RouteGetMuteTimingsApiArg = void; +export type RoutePostMuteTimingApiResponse = + /** status 201 MuteTimeInterval */ MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted; +export type RoutePostMuteTimingApiArg = { + 'X-Disable-Provenance'?: string; + muteTimeInterval: MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted; +}; +export type RouteExportMuteTimingsApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteExportMuteTimingsApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; +}; +export type RouteDeleteMuteTimingApiResponse = unknown; +export type RouteDeleteMuteTimingApiArg = { + /** Mute timing name */ + name: string; + /** Version of mute timing to use for optimistic concurrency. Leave empty to disable validation */ + version?: string; + 'X-Disable-Provenance'?: string; +}; +export type RouteGetMuteTimingApiResponse = + /** status 200 MuteTimeInterval */ MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted; +export type RouteGetMuteTimingApiArg = { + /** Mute timing name */ + name: string; +}; +export type RoutePutMuteTimingApiResponse = + /** status 202 MuteTimeInterval */ MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted; +export type RoutePutMuteTimingApiArg = { + /** Mute timing name */ + name: string; + 'X-Disable-Provenance'?: string; + muteTimeInterval: MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted; +}; +export type RouteExportMuteTimingApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteExportMuteTimingApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; + /** Mute timing name */ + name: string; +}; +export type RouteResetPolicyTreeApiResponse = /** status 202 Ack */ Ack; +export type RouteResetPolicyTreeApiArg = void; +export type RouteGetPolicyTreeApiResponse = /** status 200 Route */ Route; +export type RouteGetPolicyTreeApiArg = void; +export type RoutePutPolicyTreeApiResponse = /** status 202 Ack */ Ack; +export type RoutePutPolicyTreeApiArg = { + 'X-Disable-Provenance'?: string; + /** The new notification routing tree to use */ + route: Route; +}; +export type RouteGetPolicyTreeExportApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteGetPolicyTreeExportApiArg = void; +export type RouteGetTemplatesApiResponse = /** status 200 NotificationTemplates */ NotificationTemplates; +export type RouteGetTemplatesApiArg = void; +export type RouteDeleteTemplateApiResponse = unknown; +export type RouteDeleteTemplateApiArg = { + /** Template group name */ + name: string; + /** Version of template to use for optimistic concurrency. Leave empty to disable validation */ + version?: string; +}; +export type RouteGetTemplateApiResponse = /** status 200 NotificationTemplate */ NotificationTemplate; +export type RouteGetTemplateApiArg = { + /** Template group name */ + name: string; +}; +export type RoutePutTemplateApiResponse = /** status 202 NotificationTemplate */ NotificationTemplate; +export type RoutePutTemplateApiArg = { + /** Template group name */ + name: string; + 'X-Disable-Provenance'?: string; + notificationTemplateContent: NotificationTemplateContent; +}; +export type ListAllProvidersSettingsApiResponse = /** status 200 (empty) */ { + id?: string; + provider?: string; + settings?: { + [key: string]: any; + }; + source?: string; +}[]; +export type ListAllProvidersSettingsApiArg = void; +export type RemoveProviderSettingsApiResponse = + /** status 204 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveProviderSettingsApiArg = { + key: string; +}; +export type GetProviderSettingsApiResponse = /** status 200 (empty) */ { + id?: string; + provider?: string; + settings?: { + [key: string]: any; + }; + source?: string; +}; +export type GetProviderSettingsApiArg = { + key: string; +}; +export type UpdateProviderSettingsApiResponse = + /** status 204 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateProviderSettingsApiArg = { + key: string; + body: { + id?: string; + provider?: string; + settings?: { + [key: string]: any; + }; + }; +}; +export type SearchResultItem = { + action?: string; + basicRole?: string; + orgId?: number; + roleName?: string; + scope?: string; + teamId?: number; + userId?: number; + version?: number; +}; +export type SearchResult = { + result?: SearchResultItem[]; +}; +export type ErrorResponseBody = { + /** Error An optional detailed description of the actual error. Only included if running in developer mode. */ + error?: string; + /** a human readable version of the error */ + message: string; + /** Status An optional status to denote the cause of the error. + + For example, a 412 Precondition Failed error may include additional information of why that error happened. */ + status?: string; +}; +export type PermissionIsTheModelForAccessControlPermissions = { + action?: string; + created?: string; + scope?: string; + updated?: string; +}; +export type RoleDto = { + created?: string; + delegatable?: boolean; + description?: string; + displayName?: string; + global?: boolean; + group?: string; + hidden?: boolean; + mapped?: boolean; + name?: string; + permissions?: PermissionIsTheModelForAccessControlPermissions[]; + uid?: string; + updated?: string; + version?: number; +}; +export type CreateRoleForm = { + description?: string; + displayName?: string; + global?: boolean; + group?: string; + hidden?: boolean; + name?: string; + permissions?: PermissionIsTheModelForAccessControlPermissions[]; + uid?: string; + version?: number; +}; +export type SuccessResponseBody = { + message?: string; +}; +export type UpdateRoleCommand = { + description: string; + displayName: string; + global?: boolean; + group: string; + hidden?: boolean; + name?: string; + permissions?: PermissionIsTheModelForAccessControlPermissions[]; + version?: number; +}; +export type RoleAssignmentsDto = { + role_uid?: string; + service_accounts?: number[]; + teams?: number[]; + users?: number[]; +}; +export type SetRoleAssignmentsCommand = { + service_accounts?: number[]; + teams?: number[]; + users?: number[]; +}; +export type Status = number; +export type RolesSearchQuery = { + includeHidden?: boolean; + orgId?: number; + teamIds?: number[]; + userIds?: number[]; +}; +export type AddTeamRoleCommand = { + roleUid?: string; +}; +export type AddUserRoleCommand = { + global?: boolean; + roleUid?: string; +}; +export type SetUserRolesCommand = { + global?: boolean; + includeHidden?: boolean; + roleUids?: string[]; +}; +export type Assignments = { + builtInRoles?: boolean; + serviceAccounts?: boolean; + teams?: boolean; + users?: boolean; +}; +export type Description = { + assignments?: Assignments; + permissions?: string[]; +}; +export type ResourcePermissionDto = { + actions?: string[]; + builtInRole?: string; + id?: number; + isInherited?: boolean; + isManaged?: boolean; + isServiceAccount?: boolean; + permission?: string; + roleName?: string; + team?: string; + teamAvatarUrl?: string; + teamId?: number; + teamUid?: string; + userAvatarUrl?: string; + userId?: number; + userLogin?: string; + userUid?: string; +}; +export type SetResourcePermissionCommand = { + builtInRole?: string; + permission?: string; + teamId?: number; + userId?: number; +}; +export type SetPermissionsCommand = { + permissions?: SetResourcePermissionCommand[]; +}; +export type SetPermissionCommand = { + permission?: string; +}; +export type Duration = number; +export type FailedUser = { + Error?: string; + Login?: string; +}; +export type SyncResultHoldsTheResultOfASyncWithLdapThisGivesUsInformationOnWhichUsersWereUpdatedAndHow = { + Elapsed?: Duration; + FailedUsers?: FailedUser[]; + MissingUserIds?: number[]; + Started?: string; + UpdatedUserIds?: number[]; +}; +export type ActiveSyncStatusDto = { + enabled?: boolean; + nextSync?: string; + prevSync?: SyncResultHoldsTheResultOfASyncWithLdapThisGivesUsInformationOnWhichUsersWereUpdatedAndHow; + schedule?: string; +}; +export type SettingsBag = { + [key: string]: { + [key: string]: string; + }; +}; +export type AdminStats = { + activeAdmins?: number; + activeDevices?: number; + activeEditors?: number; + activeSessions?: number; + activeUsers?: number; + activeViewers?: number; + admins?: number; + alerts?: number; + dailyActiveAdmins?: number; + dailyActiveEditors?: number; + dailyActiveSessions?: number; + dailyActiveUsers?: number; + dailyActiveViewers?: number; + dashboards?: number; + datasources?: number; + editors?: number; + monthlyActiveUsers?: number; + orgs?: number; + playlists?: number; + snapshots?: number; + stars?: number; + tags?: number; + users?: number; + viewers?: number; +}; +export type AdminCreateUserResponse = { + id?: number; + message?: string; + uid?: string; +}; +export type Password = string; +export type AdminCreateUserForm = { + email?: string; + login?: string; + name?: string; + orgId?: number; + password?: Password; +}; +export type UserToken = { + AuthToken?: string; + AuthTokenSeen?: boolean; + ClientIp?: string; + CreatedAt?: number; + ExternalSessionId?: number; + Id?: number; + PrevAuthToken?: string; + RevokedAt?: number; + RotatedAt?: number; + SeenAt?: number; + UnhashedToken?: string; + UpdatedAt?: number; + UserAgent?: string; + UserId?: number; +}; +export type AdminUpdateUserPasswordForm = { + password?: Password; +}; +export type AdminUpdateUserPermissionsForm = { + isGrafanaAdmin?: boolean; +}; +export type QuotaDto = { + limit?: number; + org_id?: number; + target?: string; + used?: number; + user_id?: number; +}; +export type UpdateQuotaCmd = { + limit?: number; + target?: string; +}; +export type RevokeAuthTokenCmd = { + authTokenId?: number; +}; +export type Json = object; +export type Annotation = { + alertId?: number; + alertName?: string; + avatarUrl?: string; + created?: number; + /** Deprecated: Use DashboardUID and OrgID instead */ + dashboardId?: number; + dashboardUID?: string; + data?: Json; + email?: string; + id?: number; + login?: string; + newState?: string; + panelId?: number; + prevState?: string; + tags?: string[]; + text?: string; + time?: number; + timeEnd?: number; + updated?: number; + userId?: number; +}; +export type PostAnnotationsCmd = { + dashboardId?: number; + dashboardUID?: string; + data?: Json; + panelId?: number; + tags?: string[]; + text: string; + time?: number; + timeEnd?: number; +}; +export type PostGraphiteAnnotationsCmd = { + data?: string; + tags?: any; + what?: string; + when?: number; +}; +export type MassDeleteAnnotationsCmd = { + annotationId?: number; + dashboardId?: number; + dashboardUID?: string; + panelId?: number; +}; +export type TagsDtoIsTheFrontendDtoForTag = { + count?: number; + tag?: string; +}; +export type FindTagsResultIsTheResultOfATagsSearch = { + tags?: TagsDtoIsTheFrontendDtoForTag[]; +}; +export type GetAnnotationTagsResponseIsAResponseStructForFindTagsResult = { + result?: FindTagsResultIsTheResultOfATagsSearch; +}; +export type PatchAnnotationsCmd = { + data?: Json; + id?: number; + tags?: string[]; + text?: string; + time?: number; + timeEnd?: number; +}; +export type UpdateAnnotationsCmd = { + data?: Json; + id?: number; + tags?: string[]; + text?: string; + time?: number; + timeEnd?: number; +}; +export type DeviceDto = { + avatarUrl?: string; + clientIp?: string; + createdAt?: string; + deviceId?: string; + lastSeenAt?: string; + updatedAt?: string; + userAgent?: string; +}; +export type DeviceSearchHitDto = { + clientIp?: string; + createdAt?: string; + deviceId?: string; + lastSeenAt?: string; + updatedAt?: string; + userAgent?: string; +}; +export type SearchDeviceQueryResult = { + devices?: DeviceSearchHitDto[]; + page?: number; + perPage?: number; + totalCount?: number; +}; +export type CloudMigrationSessionResponseDto = { + created?: string; + slug?: string; + uid?: string; + updated?: string; +}; +export type CloudMigrationSessionListResponseDto = { + sessions?: CloudMigrationSessionResponseDto[]; +}; +export type CloudMigrationSessionRequestDto = { + authToken?: string; +}; +export type CreateSnapshotResponseDto = { + uid?: string; +}; +export type CreateSnapshotRequestDto = { + resourceTypes?: ( + | 'DASHBOARD' + | 'DATASOURCE' + | 'FOLDER' + | 'LIBRARY_ELEMENT' + | 'ALERT_RULE' + | 'ALERT_RULE_GROUP' + | 'CONTACT_POINT' + | 'NOTIFICATION_POLICY' + | 'NOTIFICATION_TEMPLATE' + | 'MUTE_TIMING' + | 'PLUGIN' + )[]; +}; +export type MigrateDataResponseItemDto = { + errorCode?: + | 'ALERT_RULES_QUOTA_REACHED' + | 'ALERT_RULES_GROUP_QUOTA_REACHED' + | 'DATASOURCE_NAME_CONFLICT' + | 'DATASOURCE_INVALID_URL' + | 'DATASOURCE_ALREADY_MANAGED' + | 'FOLDER_NAME_CONFLICT' + | 'DASHBOARD_ALREADY_MANAGED' + | 'LIBRARY_ELEMENT_NAME_CONFLICT' + | 'UNSUPPORTED_DATA_TYPE' + | 'RESOURCE_CONFLICT' + | 'UNEXPECTED_STATUS_CODE' + | 'INTERNAL_SERVICE_ERROR' + | 'GENERIC_ERROR'; + message?: string; + name?: string; + parentName?: string; + refId: string; + status: 'OK' | 'WARNING' | 'ERROR' | 'PENDING' | 'UNKNOWN'; + type: + | 'DASHBOARD' + | 'DATASOURCE' + | 'FOLDER' + | 'LIBRARY_ELEMENT' + | 'ALERT_RULE' + | 'ALERT_RULE_GROUP' + | 'CONTACT_POINT' + | 'NOTIFICATION_POLICY' + | 'NOTIFICATION_TEMPLATE' + | 'MUTE_TIMING' + | 'PLUGIN'; +}; +export type SnapshotResourceStats = { + statuses?: { + [key: string]: number; + }; + total?: number; + types?: { + [key: string]: number; + }; +}; +export type GetSnapshotResponseDto = { + created?: string; + finished?: string; + results?: MigrateDataResponseItemDto[]; + sessionUid?: string; + stats?: SnapshotResourceStats; + status?: + | 'INITIALIZING' + | 'CREATING' + | 'PENDING_UPLOAD' + | 'UPLOADING' + | 'PENDING_PROCESSING' + | 'PROCESSING' + | 'FINISHED' + | 'CANCELED' + | 'ERROR' + | 'UNKNOWN'; + uid?: string; +}; +export type SnapshotDto = { + created?: string; + finished?: string; + sessionUid?: string; + status?: + | 'INITIALIZING' + | 'CREATING' + | 'PENDING_UPLOAD' + | 'UPLOADING' + | 'PENDING_PROCESSING' + | 'PROCESSING' + | 'FINISHED' + | 'CANCELED' + | 'ERROR' + | 'UNKNOWN'; + uid?: string; +}; +export type SnapshotListResponseDto = { + snapshots?: SnapshotDto[]; +}; +export type ResourceDependencyDto = { + dependencies?: ( + | 'DASHBOARD' + | 'DATASOURCE' + | 'FOLDER' + | 'LIBRARY_ELEMENT' + | 'ALERT_RULE' + | 'ALERT_RULE_GROUP' + | 'CONTACT_POINT' + | 'NOTIFICATION_POLICY' + | 'NOTIFICATION_TEMPLATE' + | 'MUTE_TIMING' + | 'PLUGIN' + )[]; + resourceType?: + | 'DASHBOARD' + | 'DATASOURCE' + | 'FOLDER' + | 'LIBRARY_ELEMENT' + | 'ALERT_RULE' + | 'ALERT_RULE_GROUP' + | 'CONTACT_POINT' + | 'NOTIFICATION_POLICY' + | 'NOTIFICATION_TEMPLATE' + | 'MUTE_TIMING' + | 'PLUGIN'; +}; +export type ResourceDependenciesResponseDto = { + resourceDependencies?: ResourceDependencyDto[]; +}; +export type GetAccessTokenResponseDto = { + createdAt?: string; + displayName?: string; + expiresAt?: string; + firstUsedAt?: string; + id?: string; + lastUsedAt?: string; +}; +export type CreateAccessTokenResponseDto = { + token?: string; +}; +export type ConvertPrometheusResponse = { + error?: string; + errorType?: string; + status?: string; +}; +export type PublicError = { + extra?: { + [key: string]: any; + }; + message?: string; + messageId?: string; + statusCode?: number; +}; +export type ForbiddenError = { + body?: PublicError; +}; +export type PrometheusRule = { + alert?: string; + annotations?: { + [key: string]: string; + }; + expr?: string; + for?: string; + keep_firing_for?: string; + labels?: { + [key: string]: string; + }; + record?: string; +}; +export type PrometheusRuleGroup = { + interval?: Duration; + labels?: { + [key: string]: string; + }; + limit?: number; + name?: string; + query_offset?: string; + rules?: PrometheusRule[]; +}; +export type DashboardSnapshotDto = { + created?: string; + expires?: string; + external?: boolean; + externalUrl?: string; + key?: string; + name?: string; + updated?: string; +}; +export type CalculateDiffTarget = { + dashboardId?: number; + unsavedDashboard?: Json; + version?: number; +}; +export type SaveDashboardCommand = { + UpdatedAt?: string; + dashboard?: Json; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderUid?: string; + isFolder?: boolean; + message?: string; + overwrite?: boolean; + userId?: number; +}; +export type AnnotationActions = { + canAdd?: boolean; + canDelete?: boolean; + canEdit?: boolean; +}; +export type AnnotationPermission = { + dashboard?: AnnotationActions; + organization?: AnnotationActions; +}; +export type DashboardMeta = { + annotationsPermissions?: AnnotationPermission; + apiVersion?: string; + canAdmin?: boolean; + canDelete?: boolean; + canEdit?: boolean; + canSave?: boolean; + canStar?: boolean; + created?: string; + createdBy?: string; + expires?: string; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderTitle?: string; + folderUid?: string; + folderUrl?: string; + hasAcl?: boolean; + isFolder?: boolean; + isSnapshot?: boolean; + isStarred?: boolean; + provisioned?: boolean; + provisionedExternalId?: string; + publicDashboardEnabled?: boolean; + slug?: string; + type?: string; + updated?: string; + updatedBy?: string; + url?: string; + version?: number; +}; +export type GetHomeDashboardResponse = { + dashboard?: Json; + meta?: DashboardMeta; +} & { + redirectUri?: string; +}; +export type ImportDashboardResponseResponseObjectReturnedWhenImportingADashboard = { + dashboardId?: number; + description?: string; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderUid?: string; + imported?: boolean; + importedRevision?: number; + importedUri?: string; + importedUrl?: string; + path?: string; + pluginId?: string; + removed?: boolean; + revision?: number; + slug?: string; + title?: string; + uid?: string; +}; +export type ImportDashboardInputDefinitionOfInputParametersWhenImportingADashboard = { + name?: string; + pluginId?: string; + type?: string; + value?: string; +}; +export type ImportDashboardRequestRequestObjectForImportingADashboard = { + dashboard?: Json; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderUid?: string; + inputs?: ImportDashboardInputDefinitionOfInputParametersWhenImportingADashboard[]; + overwrite?: boolean; + path?: string; + pluginId?: string; +}; +export type PublicDashboardListResponse = { + accessToken?: string; + dashboardUid?: string; + isEnabled?: boolean; + slug?: string; + title?: string; + uid?: string; +}; +export type PublicDashboardListResponseWithPagination = { + page?: number; + perPage?: number; + publicDashboards?: PublicDashboardListResponse[]; + totalCount?: number; +}; +export type PublicError2 = { + /** Extra Additional information about the error */ + extra?: { + [key: string]: any; + }; + /** Message A human readable message */ + message?: string; + /** MessageID A unique identifier for the error */ + messageId: string; + /** StatusCode The HTTP status code returned */ + statusCode: number; +}; +export type DashboardTagCloudItem = { + count?: number; + term?: string; +}; +export type EmailDto = { + recipient?: string; + uid?: string; +}; +export type ShareType = string; +export type PublicDashboard = { + accessToken?: string; + annotationsEnabled?: boolean; + createdAt?: string; + createdBy?: number; + dashboardUid?: string; + isEnabled?: boolean; + recipients?: EmailDto[]; + share?: ShareType; + timeSelectionEnabled?: boolean; + uid?: string; + updatedAt?: string; + updatedBy?: number; +}; +export type PublicDashboardDto = { + accessToken?: string; + annotationsEnabled?: boolean; + isEnabled?: boolean; + share?: ShareType; + timeSelectionEnabled?: boolean; + uid?: string; +}; +export type DashboardFullWithMeta = { + dashboard?: Json; + meta?: DashboardMeta; +}; +export type PermissionType = number; +export type DashboardAclInfoDto = { + created?: string; + dashboardId?: number; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderUid?: string; + inherited?: boolean; + isFolder?: boolean; + permission?: PermissionType; + permissionName?: string; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; + slug?: string; + team?: string; + teamAvatarUrl?: string; + teamEmail?: string; + teamId?: number; + teamUid?: string; + title?: string; + uid?: string; + updated?: string; + url?: string; + userAvatarUrl?: string; + userEmail?: string; + userId?: number; + userLogin?: string; + userUid?: string; +}; +export type DashboardAclUpdateItem = { + permission?: PermissionType; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; + teamId?: number; + userId?: number; +}; +export type UpdateDashboardAclCommand = { + items?: DashboardAclUpdateItem[]; +}; +export type RestoreDashboardVersionCommand = { + version?: number; +}; +export type DashboardVersionMeta = { + created?: string; + createdBy?: string; + dashboardId?: number; + data?: Json; + id?: number; + message?: string; + parentVersion?: number; + restoredFrom?: number; + uid?: string; + version?: number; +}; +export type DashboardVersionResponseMeta = { + continueToken?: string; + versions?: DashboardVersionMeta[]; +}; +export type DsAccess = string; +export type DataSourceListItemDto = { + access?: DsAccess; + basicAuth?: boolean; + database?: string; + id?: number; + isDefault?: boolean; + jsonData?: Json; + name?: string; + orgId?: number; + readOnly?: boolean; + type?: string; + typeLogoUrl?: string; + typeName?: string; + uid?: string; + url?: string; + user?: string; +}; +export type DataSourceList = DataSourceListItemDto[]; +export type Metadata = { + [key: string]: boolean; +}; +export type DataSource = { + access?: DsAccess; + accessControl?: Metadata; + basicAuth?: boolean; + basicAuthUser?: string; + database?: string; + id?: number; + isDefault?: boolean; + jsonData?: Json; + name?: string; + orgId?: number; + readOnly?: boolean; + secureJsonFields?: { + [key: string]: boolean; + }; + type?: string; + typeLogoUrl?: string; + uid?: string; + url?: string; + user?: string; + version?: number; + withCredentials?: boolean; +}; +export type AddDataSourceCommand = { + access?: DsAccess; + basicAuth?: boolean; + basicAuthUser?: string; + database?: string; + isDefault?: boolean; + jsonData?: Json; + name?: string; + secureJsonData?: { + [key: string]: string; + }; + type?: string; + uid?: string; + url?: string; + user?: string; + withCredentials?: boolean; +}; +export type Transformation = { + expression?: string; + field?: string; + mapValue?: string; + type?: 'regex' | 'logfmt'; +}; +export type Transformations = Transformation[]; +export type CorrelationType = string; +export type CorrelationConfig = { + /** Field used to attach the correlation link */ + field: string; + /** Target data query */ + target: { + [key: string]: any; + }; + transformations?: Transformations; + type?: CorrelationType; +}; +export type Correlation = { + config?: CorrelationConfig; + /** Description of the correlation */ + description?: string; + /** Label identifying the correlation */ + label?: string; + /** OrgID of the data source the correlation originates from */ + orgId?: number; + /** Provisioned True if the correlation was created during provisioning */ + provisioned?: boolean; + /** UID of the data source the correlation originates from */ + sourceUID?: string; + /** UID of the data source the correlation points to */ + targetUID?: string; + type?: CorrelationType; + /** Unique identifier of the correlation */ + uid?: string; +}; +export type CreateCorrelationResponseBody = { + message?: string; + result?: Correlation; +}; +export type CreateCorrelationCommand = { + config?: CorrelationConfig; + /** Optional description of the correlation */ + description?: string; + /** Optional label identifying the correlation */ + label?: string; + /** True if correlation was created with provisioning. This makes it read-only. */ + provisioned?: boolean; + /** Target data source UID to which the correlation is created. required if type = query */ + targetUID?: string; + type?: CorrelationType; +}; +export type UpdateCorrelationResponseBody = { + message?: string; + result?: Correlation; +}; +export type CorrelationConfigUpdateDto = { + /** Field used to attach the correlation link */ + field?: string; + /** Target data query */ + target?: { + [key: string]: any; + }; + /** Source data transformations */ + transformations?: Transformation[]; +}; +export type UpdateCorrelationCommand = { + config?: CorrelationConfigUpdateDto; + /** Optional description of the correlation */ + description?: string; + /** Optional label identifying the correlation */ + label?: string; + type?: CorrelationType; +}; +export type UpdateDataSourceCommand = { + access?: DsAccess; + basicAuth?: boolean; + basicAuthUser?: string; + database?: string; + isDefault?: boolean; + jsonData?: Json; + name?: string; + secureJsonData?: { + [key: string]: string; + }; + type?: string; + uid?: string; + url?: string; + user?: string; + /** The previous version -- used for optimistic locking */ + version?: number; + withCredentials?: boolean; +}; +export type DeleteCorrelationResponseBody = { + message?: string; +}; +export type TeamLbacRule = { + rules?: string[]; + teamId?: string; + teamUid?: string; +}; +export type TeamLbacRules = { + rules?: TeamLbacRule[]; +}; +export type UpdateTeamLbacCommand = { + rules?: TeamLbacRule[]; +}; +export type CacheConfigResponse = { + created?: string; + /** Fields that can be set by the API caller - read/write */ + dataSourceID?: number; + dataSourceUID?: string; + /** These are returned by the HTTP API, but are managed internally - read-only + Note: 'created' and 'updated' are special properties managed automatically by xorm, but we are setting them manually */ + defaultTTLMs?: number; + enabled?: boolean; + message?: string; + /** TTL MS, or "time to live", is how long a cached item will stay in the cache before it is removed (in milliseconds) */ + ttlQueriesMs?: number; + ttlResourcesMs?: number; + updated?: string; + /** If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini */ + useDefaultTTL?: boolean; +}; +export type CacheConfigSetter = { + dataSourceID?: number; + dataSourceUID?: string; + enabled?: boolean; + /** TTL MS, or "time to live", is how long a cached item will stay in the cache before it is removed (in milliseconds) */ + ttlQueriesMs?: number; + ttlResourcesMs?: number; + /** If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini */ + useDefaultTTL?: boolean; +}; +export type SourceTypeDefinesTheStatusSource = string; +export type ExplorePanelsState = any; +export type TimeRange = { + from?: string; + to?: string; +}; +export type SupportedTransformationTypes = string; +export type LinkTransformationConfig = { + expression?: string; + field?: string; + mapValue?: string; + type?: SupportedTransformationTypes; +}; +export type InternalDataLink = { + datasourceName?: string; + datasourceUid?: string; + panelsState?: ExplorePanelsState; + query?: any; + timeRange?: TimeRange; + transformations?: LinkTransformationConfig[]; +}; +export type DataLink = { + internal?: InternalDataLink; + targetBlank?: boolean; + title?: string; + url?: string; +}; +export type ValueMapping = object; +export type ValueMappings = ValueMapping[]; +export type ConfFloat64 = number; +export type ThresholdsMode = string; +export type Threshold = { + color?: string; + state?: string; + value?: ConfFloat64; +}; +export type ThresholdsConfig = { + mode?: ThresholdsMode; + /** Must be sorted by 'value', first value is always -Infinity */ + steps?: Threshold[]; +}; +export type EnumFieldConfig = { + /** Color is the color value for a given index (empty is undefined) */ + color?: string[]; + /** Description of the enum state */ + description?: string[]; + /** Icon supports setting an icon for a given index value */ + icon?: string[]; + /** Value is the string display value for a given index */ + text?: string[]; +}; +export type FieldTypeConfig = { + enum?: EnumFieldConfig; +}; +export type FieldConfigRepresentsTheDisplayPropertiesForAField = { + /** Map values to a display color + NOTE: this interface is under development in the frontend... so simple map for now */ + color?: { + [key: string]: any; + }; + /** Panel Specific Values */ + custom?: { + [key: string]: any; + }; + decimals?: number; + /** Description is human readable field metadata */ + description?: string; + /** DisplayName overrides Grafana default naming, should not be used from a data source */ + displayName?: string; + /** DisplayNameFromDS overrides Grafana default naming strategy. */ + displayNameFromDS?: string; + /** Filterable indicates if the Field's data can be filtered by additional calls. */ + filterable?: boolean; + /** Interval indicates the expected regular step between values in the series. + When an interval exists, consumers can identify "missing" values when the expected value is not present. + The grafana timeseries visualization will render disconnected values when missing values are found it the time field. + The interval uses the same units as the values. For time.Time, this is defined in milliseconds. */ + interval?: number; + /** The behavior when clicking on a result */ + links?: DataLink[]; + mappings?: ValueMappings; + max?: ConfFloat64; + min?: ConfFloat64; + /** Alternative to empty string */ + noValue?: string; + /** Path is an explicit path to the field in the datasource. When the frame meta includes a path, + this will default to `${frame.meta.path}/${field.name} + + When defined, this value can be used as an identifier within the datasource scope, and + may be used as an identifier to update values in a subsequent request */ + path?: string; + thresholds?: ThresholdsConfig; + type?: FieldTypeConfig; + /** Numeric Options */ + unit?: string; + /** Writeable indicates that the datasource knows how to update this value */ + writeable?: boolean; +}; +export type FrameLabels = { + [key: string]: string; +}; +export type FieldRepresentsATypedColumnOfDataWithinAFrame = { + config?: FieldConfigRepresentsTheDisplayPropertiesForAField; + labels?: FrameLabels; + /** Name is default identifier of the field. The name does not have to be unique, but the combination + of name and Labels should be unique for proper behavior in all situations. */ + name?: string; +}; +export type DataTopicIsUsedToIdentifyWhichTopicTheFrameShouldBeAssignedTo = string; +export type InspectTypeIsATypeForTheInspectPropertyOfANotice = number; +export type NoticeSeverityIsATypeForTheSeverityPropertyOfANotice = number; +export type NoticeProvidesAStructureForPresentingNotificationsInGrafanasUserInterface = { + inspect?: InspectTypeIsATypeForTheInspectPropertyOfANotice; + /** Link is an optional link for display in the user interface and can be an + absolute URL or a path relative to Grafana's root url. */ + link?: string; + severity?: NoticeSeverityIsATypeForTheSeverityPropertyOfANotice; + /** Text is freeform descriptive text for the notice. */ + text?: string; +}; +export type VisTypeIsUsedToIndicateHowTheDataShouldBeVisualizedInExplore = string; +export type QueryStatIsUsedForStoringArbitraryStatisticsMetadataRelatedToAQueryAndItsResultEGTotalRequestTimeDataProcessingTime = + { + /** Map values to a display color + NOTE: this interface is under development in the frontend... so simple map for now */ + color?: { + [key: string]: any; + }; + /** Panel Specific Values */ + custom?: { + [key: string]: any; + }; + decimals?: number; + /** Description is human readable field metadata */ + description?: string; + /** DisplayName overrides Grafana default naming, should not be used from a data source */ + displayName?: string; + /** DisplayNameFromDS overrides Grafana default naming strategy. */ + displayNameFromDS?: string; + /** Filterable indicates if the Field's data can be filtered by additional calls. */ + filterable?: boolean; + /** Interval indicates the expected regular step between values in the series. + When an interval exists, consumers can identify "missing" values when the expected value is not present. + The grafana timeseries visualization will render disconnected values when missing values are found it the time field. + The interval uses the same units as the values. For time.Time, this is defined in milliseconds. */ + interval?: number; + /** The behavior when clicking on a result */ + links?: DataLink[]; + mappings?: ValueMappings; + max?: ConfFloat64; + min?: ConfFloat64; + /** Alternative to empty string */ + noValue?: string; + /** Path is an explicit path to the field in the datasource. When the frame meta includes a path, + this will default to `${frame.meta.path}/${field.name} + + When defined, this value can be used as an identifier within the datasource scope, and + may be used as an identifier to update values in a subsequent request */ + path?: string; + thresholds?: ThresholdsConfig; + type?: FieldTypeConfig; + /** Numeric Options */ + unit?: string; + value?: number; + /** Writeable indicates that the datasource knows how to update this value */ + writeable?: boolean; + }; +export type FrameType = string; +export type FrameTypeIsA2NumberVersionMajorMinor = number[]; +export type FrameMetaMatches = { + /** Channel is the path to a stream in grafana live that has real-time updates for this data. */ + channel?: string; + /** Custom datasource specific values. */ + custom?: any; + dataTopic?: DataTopicIsUsedToIdentifyWhichTopicTheFrameShouldBeAssignedTo; + /** ExecutedQueryString is the raw query sent to the underlying system. All macros and templating + have been applied. When metadata contains this value, it will be shown in the query inspector. */ + executedQueryString?: string; + /** Notices provide additional information about the data in the Frame that + Grafana can display to the user in the user interface. */ + notices?: NoticeProvidesAStructureForPresentingNotificationsInGrafanasUserInterface[]; + /** Path is a browsable path on the datasource. */ + path?: string; + /** PathSeparator defines the separator pattern to decode a hierarchy. The default separator is '/'. */ + pathSeparator?: string; + /** PreferredVisualizationPluginId sets the panel plugin id to use to render the data when using Explore. If + the plugin cannot be found will fall back to PreferredVisualization. */ + preferredVisualisationPluginId?: string; + preferredVisualisationType?: VisTypeIsUsedToIndicateHowTheDataShouldBeVisualizedInExplore; + /** Stats is an array of query result statistics. */ + stats?: QueryStatIsUsedForStoringArbitraryStatisticsMetadataRelatedToAQueryAndItsResultEGTotalRequestTimeDataProcessingTime[]; + type?: FrameType; + typeVersion?: FrameTypeIsA2NumberVersionMajorMinor; + /** Array of field indices which values create a unique id for each row. Ideally this should be globally unique ID + but that isn't guarantied. Should help with keeping track and deduplicating rows in visualizations, especially + with streaming data with frequent updates. */ + uniqueRowIdFields?: number[]; +}; +export type FrameIsAColumnarDataStructureWhereEachColumnIsAField = { + /** Fields are the columns of a frame. + All Fields must be of the same the length when marshalling the Frame for transmission. + There should be no `nil` entries in the Fields slice (making them pointers was a mistake). */ + Fields?: FieldRepresentsATypedColumnOfDataWithinAFrame[]; + Meta?: FrameMetaMatches; + /** Name is used in some Grafana visualizations. */ + Name?: string; + /** RefID is a property that can be set to match a Frame to its originating query. */ + RefID?: string; +}; +export type FramesIsASliceOfFramePointers = FrameIsAColumnarDataStructureWhereEachColumnIsAField[]; +export type DataResponseContainsTheResultsFromADataQuery = { + /** Error is a property to be set if the corresponding DataQuery has an error. */ + Error?: string; + ErrorSource?: SourceTypeDefinesTheStatusSource; + Frames?: FramesIsASliceOfFramePointers; + Status?: Status; +}; +export type ResponsesIsAMapOfRefIDsUniqueQueryIdToDataResponses = { + [key: string]: DataResponseContainsTheResultsFromADataQuery; +}; +export type QueryDataResponseContainsTheResultsFromAQueryDataRequest = { + results?: ResponsesIsAMapOfRefIDsUniqueQueryIdToDataResponses; +}; +export type MetricRequest = { + debug?: boolean; + /** From Start time in epoch timestamps in milliseconds or relative using Grafana time units. */ + from: string; + /** queries.refId – Specifies an identifier of the query. Is optional and default to “A”. + queries.datasourceId – Specifies the data source to be queried. Each query in the request must have an unique datasourceId. + queries.maxDataPoints - Species maximum amount of data points that dashboard panel can render. Is optional and default to 100. + queries.intervalMs - Specifies the time interval in milliseconds of time series. Is optional and defaults to 1000. */ + queries: Json[]; + /** To End time in epoch timestamps in milliseconds or relative using Grafana time units. */ + to: string; +}; +export type ManagerKindIsTheTypeOfManagerWhichIsResponsibleForManagingTheResource = string; +export type FolderSearchHit = { + id?: number; + managedBy?: ManagerKindIsTheTypeOfManagerWhichIsResponsibleForManagingTheResource; + parentUid?: string; + title?: string; + uid?: string; +}; +export type Folder = { + accessControl?: Metadata; + canAdmin?: boolean; + canDelete?: boolean; + canEdit?: boolean; + canSave?: boolean; + created?: string; + createdBy?: string; + hasAcl?: boolean; + /** Deprecated: use UID instead */ + id?: number; + managedBy?: ManagerKindIsTheTypeOfManagerWhichIsResponsibleForManagingTheResource; + orgId?: number; + /** only used if nested folders are enabled */ + parentUid?: string; + /** the parent folders starting from the root going down */ + parents?: Folder[]; + title?: string; + uid?: string; + updated?: string; + updatedBy?: string; + url?: string; + version?: number; +}; +export type CreateFolderCommand = { + description?: string; + parentUid?: string; + title?: string; + uid?: string; +}; +export type UpdateFolderCommand = { + /** NewDescription it's an optional parameter used for overriding the existing folder description */ + description?: string; + /** Overwrite only used by the legacy folder implementation */ + overwrite?: boolean; + /** NewTitle it's an optional parameter used for overriding the existing folder title */ + title?: string; + /** Version only used by the legacy folder implementation */ + version?: number; +}; +export type DescendantCounts = { + [key: string]: number; +}; +export type MoveFolderCommand = { + parentUid?: string; +}; +export type Group = { + groupID?: string; + mappings?: any; +}; +export type GetGroupsResponse = { + groups?: Group[]; + total?: number; +}; +export type MessageResponse = { + message?: string; +}; +export type GroupAttributes = { + roles?: string[]; +}; +export type HealthResponse = { + commit?: string; + database?: string; + enterpriseCommit?: string; + version?: string; +}; +export type LibraryElementDtoMetaUser = { + avatarUrl?: string; + id?: number; + name?: string; +}; +export type LibraryElementDtoMetaIsTheMetaInformationForLibraryElementDto = { + connectedDashboards?: number; + created?: string; + createdBy?: LibraryElementDtoMetaUser; + folderName?: string; + folderUid?: string; + updated?: string; + updatedBy?: LibraryElementDtoMetaUser; +}; +export type LibraryElementDtoIsTheFrontendDtoForEntities = { + description?: string; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderUid?: string; + id?: number; + kind?: number; + meta?: LibraryElementDtoMetaIsTheMetaInformationForLibraryElementDto; + model?: object; + name?: string; + orgId?: number; + schemaVersion?: number; + type?: string; + uid?: string; + version?: number; +}; +export type LibraryElementSearchResultIsTheSearchResultForEntities = { + elements?: LibraryElementDtoIsTheFrontendDtoForEntities[]; + page?: number; + perPage?: number; + totalCount?: number; +}; +export type LibraryElementSearchResponseIsAResponseStructForLibraryElementSearchResult = { + result?: LibraryElementSearchResultIsTheSearchResultForEntities; +}; +export type LibraryElementResponseIsAResponseStructForLibraryElementDto = { + result?: LibraryElementDtoIsTheFrontendDtoForEntities; +}; +export type CreateLibraryElementCommand = { + /** ID of the folder where the library element is stored. + + Deprecated: use FolderUID instead */ + folderId?: number; + /** UID of the folder where the library element is stored. */ + folderUid?: string; + /** Kind of element to create, Use 1 for library panels or 2 for c. + Description: + 1 - library panels */ + kind?: 1; + /** The JSON model for the library element. */ + model?: object; + /** Name of the library element. */ + name?: string; + uid?: string; +}; +export type LibraryElementArrayResponseIsAResponseStructForAnArrayOfLibraryElementDto = { + result?: LibraryElementDtoIsTheFrontendDtoForEntities[]; +}; +export type PatchLibraryElementCommand = { + /** ID of the folder where the library element is stored. + + Deprecated: use FolderUID instead */ + folderId?: number; + /** UID of the folder where the library element is stored. */ + folderUid?: string; + /** Kind of element to create, Use 1 for library panels or 2 for c. + Description: + 1 - library panels */ + kind?: 1; + /** The JSON model for the library element. */ + model?: object; + /** Name of the library element. */ + name?: string; + uid?: string; + /** Version of the library element you are updating. */ + version?: number; +}; +export type LibraryElementConnectionDtoIsTheFrontendDtoForElementConnections = { + connectionId?: number; + connectionUid?: string; + created?: string; + createdBy?: LibraryElementDtoMetaUser; + elementId?: number; + /** Deprecated: this field will be removed in the future */ + id?: number; + kind?: number; +}; +export type LibraryElementConnectionsResponseIsAResponseStructForAnArrayOfLibraryElementConnectionDto = { + result?: LibraryElementConnectionDtoIsTheFrontendDtoForElementConnections[]; +}; +export type ActiveUserStats = { + active_admins_and_editors?: number; + active_anonymous_devices?: number; + active_users?: number; + active_viewers?: number; +}; +export type DeleteTokenCommand = { + instance?: string; +}; +export type TokenStatus = number; +export type Token = { + account?: string; + anonymousRatio?: number; + company?: string; + details_url?: string; + exp?: number; + iat?: number; + included_users?: number; + iss?: string; + jti?: string; + lexp?: number; + lic_exp_warn_days?: number; + lid?: string; + limit_by?: string; + max_concurrent_user_sessions?: number; + nbf?: number; + prod?: string[]; + slug?: string; + status?: TokenStatus; + sub?: string; + tok_exp_warn_days?: number; + trial?: boolean; + trial_exp?: number; + update_days?: number; + usage_billing?: boolean; +}; +export type Address = { + address1?: string; + address2?: string; + city?: string; + country?: string; + state?: string; + zipCode?: string; +}; +export type OrgDetailsDto = { + address?: Address; + id?: number; + name?: string; +}; +export type UpdateOrgForm = { + name?: string; +}; +export type UpdateOrgAddressForm = { + address1?: string; + address2?: string; + city?: string; + country?: string; + state?: string; + zipcode?: string; +}; +export type TempUserStatus = string; +export type TempUserDto = { + code?: string; + createdOn?: string; + email?: string; + emailSent?: boolean; + emailSentOn?: string; + id?: number; + invitedByEmail?: string; + invitedByLogin?: string; + invitedByName?: string; + name?: string; + orgId?: number; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; + status?: TempUserStatus; + url?: string; +}; +export type AddInviteForm = { + loginOrEmail?: string; + name?: string; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; + sendEmail?: boolean; +}; +export type PreferencesCookiePreferences = { + analytics?: any; + functional?: any; + performance?: any; +}; +export type PreferencesNavbarPreference = { + bookmarkUrls?: string[]; +}; +export type PreferencesQueryHistoryPreference = { + /** one of: '' | 'query' | 'starred'; */ + homeTab?: string; +}; +export type PreferencesSpec = { + cookiePreferences?: PreferencesCookiePreferences; + /** UID for the home dashboard */ + homeDashboardUID?: string; + /** Selected language (beta) */ + language?: string; + navbar?: PreferencesNavbarPreference; + queryHistory?: PreferencesQueryHistoryPreference; + /** Selected locale (beta) */ + regionalFormat?: string; + /** light, dark, empty is default */ + theme?: string; + /** The timezone selection + TODO: this should use the timezone defined in common */ + timezone?: string; + /** day of the week (sunday, monday, etc) */ + weekStart?: string; +}; +export type CookieType = string; +export type NavbarPreference = { + bookmarkUrls?: string[]; +}; +export type QueryHistoryPreference = { + homeTab?: string; +}; +export type PatchPrefsCmd = { + cookies?: CookieType[]; + /** The numerical :id of a favorited dashboard */ + homeDashboardId?: number; + homeDashboardUID?: string; + language?: string; + navbar?: NavbarPreference; + queryHistory?: QueryHistoryPreference; + regionalFormat?: string; + theme?: 'light' | 'dark'; + timezone?: 'utc' | 'browser'; + weekStart?: string; +}; +export type UpdatePrefsCmd = { + cookies?: CookieType[]; + /** The numerical :id of a favorited dashboard */ + homeDashboardId?: number; + homeDashboardUID?: string; + language?: string; + navbar?: NavbarPreference; + queryHistory?: QueryHistoryPreference; + regionalFormat?: string; + theme?: 'light' | 'dark' | 'system'; + timezone?: 'utc' | 'browser'; + weekStart?: string; +}; +export type OrgUserDto = { + accessControl?: { + [key: string]: boolean; + }; + authLabels?: string[]; + avatarUrl?: string; + email?: string; + isDisabled?: boolean; + isExternallySynced?: boolean; + isProvisioned?: boolean; + lastSeenAt?: string; + lastSeenAtAge?: string; + login?: string; + name?: string; + orgId?: number; + role?: string; + uid?: string; + userId?: number; +}; +export type AddOrgUserCommand = { + loginOrEmail?: string; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; +}; +export type UserLookupDto = { + avatarUrl?: string; + login?: string; + uid?: string; + userId?: number; +}; +export type UpdateOrgUserCommand = { + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; +}; +export type OrgDto = { + id?: number; + name?: string; +}; +export type CreateOrgCommand = { + name?: string; +}; +export type SearchOrgUsersQueryResult = { + orgUsers?: OrgUserDto[]; + page?: number; + perPage?: number; + totalCount?: number; +}; +export type Playlist = { + id?: number; + interval?: string; + name?: string; + uid?: string; +}; +export type Playlists = Playlist[]; +export type PlaylistItem = { + Id?: number; + PlaylistId?: number; + order?: number; + title?: string; + type?: string; + value?: string; +}; +export type CreatePlaylistCommand = { + interval?: string; + items?: PlaylistItem[]; + name?: string; +}; +export type PlaylistItemDto = { + /** Title is an unused property -- it will be removed in the future */ + title?: string; + /** Type of the item. */ + type?: string; + /** Value depends on type and describes the playlist item. + + dashboard_by_id: The value is an internal numerical identifier set by Grafana. This + is not portable as the numerical identifier is non-deterministic between different instances. + Will be replaced by dashboard_by_uid in the future. (deprecated) + dashboard_by_tag: The value is a tag which is set on any number of dashboards. All + dashboards behind the tag will be added to the playlist. + dashboard_by_uid: The value is the dashboard UID */ + value?: string; +}; +export type PlaylistDto = { + /** Interval sets the time between switching views in a playlist. */ + interval?: string; + /** The ordered list of items that the playlist will iterate over. */ + items?: PlaylistItemDto[]; + /** Name of the playlist. */ + name?: string; + /** Unique playlist identifier. Generated on creation, either by the + creator of the playlist of by the application. */ + uid?: string; +}; +export type UpdatePlaylistCommand = { + interval?: string; + items?: PlaylistItem[]; + name?: string; + uid?: string; +}; +export type DataSourceRef = { + /** The plugin type-id */ + type?: string; + /** Specific datasource instance */ + uid?: string; +}; +export type AnnotationPanelFilter = { + /** Should the specified panels be included or excluded */ + exclude?: boolean; + /** Panel IDs that should be included or excluded */ + ids?: number[]; +}; +export type AnnotationTarget = { + /** Only required/valid for the grafana datasource... + but code+tests is already depending on it so hard to change */ + limit?: number; + /** Only required/valid for the grafana datasource... + but code+tests is already depending on it so hard to change */ + matchAny?: boolean; + /** Only required/valid for the grafana datasource... + but code+tests is already depending on it so hard to change */ + tags?: string[]; + /** Only required/valid for the grafana datasource... + but code+tests is already depending on it so hard to change */ + type?: string; +}; +export type AnnotationQuery = { + /** Set to 1 for the standard annotation query all dashboards have by default. */ + builtIn?: number; + datasource?: DataSourceRef; + /** When enabled the annotation query is issued with every dashboard refresh */ + enable?: boolean; + filter?: AnnotationPanelFilter; + /** Annotation queries can be toggled on or off at the top of the dashboard. + When hide is true, the toggle is not shown in the dashboard. */ + hide?: boolean; + /** Color to use for the annotation event markers */ + iconColor?: string; + /** Name of annotation. */ + name?: string; + target?: AnnotationTarget; + /** TODO -- this should not exist here, it is based on the --grafana-- datasource */ + type?: string; +}; +export type AnnotationEvent = { + color?: string; + dashboardId?: number; + dashboardUID?: string; + id?: number; + isRegion?: boolean; + panelId?: number; + source?: AnnotationQuery; + tags?: string[]; + text?: string; + time?: number; + timeEnd?: number; +}; +export type QueryHistoryDto = { + comment?: string; + createdAt?: number; + createdBy?: number; + datasourceUid?: string; + queries?: Json; + starred?: boolean; + uid?: string; +}; +export type QueryHistorySearchResult = { + page?: number; + perPage?: number; + queryHistory?: QueryHistoryDto[]; + totalCount?: number; +}; +export type QueryHistorySearchResponse = { + result?: QueryHistorySearchResult; +}; +export type QueryHistoryResponse = { + result?: QueryHistoryDto; +}; +export type CreateQueryInQueryHistoryCommand = { + /** UID of the data source for which are queries stored. */ + datasourceUid?: string; + queries: Json; +}; +export type QueryHistoryDeleteQueryResponse = { + id?: number; + message?: string; +}; +export type PatchQueryCommentInQueryHistoryCommand = { + /** Updated comment */ + comment?: string; +}; +export type RecordingRuleJson = { + active?: boolean; + count?: boolean; + description?: string; + dest_data_source_uid?: string; + id?: string; + interval?: number; + name?: string; + prom_name?: string; + queries?: { + [key: string]: any; + }[]; + range?: number; + target_ref_id?: string; +}; +export type PrometheusRemoteWriteTargetJson = { + data_source_uid?: string; + id?: string; + remote_write_path?: string; +}; +export type ReportDashboardId = { + id?: number; + name?: string; + uid?: string; +}; +export type ReportTimeRange = { + from?: string; + to?: string; +}; +export type ReportDashboard = { + dashboard?: ReportDashboardId; + reportVariables?: object; + timeRange?: ReportTimeRange; +}; +export type Type = string; +export type ReportOptions = { + layout?: string; + orientation?: string; + pdfCombineOneFile?: boolean; + pdfShowTemplateVariables?: boolean; + timeRange?: ReportTimeRange; +}; +export type ReportSchedule = { + dayOfMonth?: string; + endDate?: string; + frequency?: string; + intervalAmount?: number; + intervalFrequency?: string; + startDate?: string; + timeZone?: string; + workdaysOnly?: boolean; +}; +export type State = string; +export type Report = { + created?: string; + dashboards?: ReportDashboard[]; + enableCsv?: boolean; + enableDashboardUrl?: boolean; + formats?: Type[]; + id?: number; + message?: string; + name?: string; + options?: ReportOptions; + orgId?: number; + recipients?: string; + replyTo?: string; + scaleFactor?: number; + schedule?: ReportSchedule; + state?: State; + subject?: string; + uid?: string; + updated?: string; + userId?: number; +}; +export type CreateOrUpdateReport = { + dashboards?: ReportDashboard[]; + enableCsv?: boolean; + enableDashboardUrl?: boolean; + formats?: Type[]; + message?: string; + name?: string; + options?: ReportOptions; + recipients?: string; + replyTo?: string; + scaleFactor?: number; + schedule?: ReportSchedule; + state?: State; + subject?: string; +}; +export type ReportEmail = { + /** Comma-separated list of emails to which to send the report to. */ + emails?: string; + /** Send the report to the emails specified in the report. Required if emails is not present. */ + id?: string; + /** Send the report to the emails specified in the report. Required if emails is not present. */ + useEmailsFromReport?: boolean; +}; +export type ReportBrandingOptions = { + emailFooterLink?: string; + emailFooterMode?: string; + emailFooterText?: string; + emailLogoUrl?: string; + reportLogoUrl?: string; +}; +export type ReportSettings = { + branding?: ReportBrandingOptions; + embeddedImageTheme?: string; + id?: number; + orgId?: number; + pdfTheme?: string; + userId?: number; +}; +export type HitType = string; +export type Hit = { + description?: string; + folderId?: number; + folderTitle?: string; + folderUid?: string; + folderUrl?: string; + id?: number; + isDeleted?: boolean; + isStarred?: boolean; + orgId?: number; + permanentlyDeleteDate?: string; + slug?: string; + sortMeta?: number; + sortMetaName?: string; + tags?: string[]; + title?: string; + type?: HitType; + uid?: string; + uri?: string; + url?: string; +}; +export type HitList = Hit[]; +export type ServiceAccountDto = { + accessControl?: { + [key: string]: boolean; + }; + avatarUrl?: string; + id?: number; + isDisabled?: boolean; + isExternal?: boolean; + login?: string; + name?: string; + orgId?: number; + role?: string; + tokens?: number; + uid?: string; +}; +export type CreateServiceAccountForm = { + isDisabled?: boolean; + name?: string; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; +}; +export type SearchOrgServiceAccountsResult = { + page?: number; + perPage?: number; + serviceAccounts?: ServiceAccountDto[]; + /** It can be used for pagination of the user list + E.g. if totalCount is equal to 100 users and + the perpage parameter is set to 10 then there are 10 pages of users. */ + totalCount?: number; +}; +export type ServiceAccountProfileDto = { + accessControl?: { + [key: string]: boolean; + }; + avatarUrl?: string; + createdAt?: string; + id?: number; + isDisabled?: boolean; + isExternal?: boolean; + login?: string; + name?: string; + orgId?: number; + requiredBy?: string; + role?: string; + teams?: string[]; + tokens?: number; + uid?: string; + updatedAt?: string; +}; +export type UpdateServiceAccountForm = { + isDisabled?: boolean; + name?: string; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; + serviceAccountId?: number; +}; +export type TokenDto = { + created?: string; + expiration?: string; + hasExpired?: boolean; + id?: number; + isRevoked?: boolean; + lastUsedAt?: string; + name?: string; + secondsUntilExpiration?: number; +}; +export type NewApiKeyResult = { + id?: number; + key?: string; + name?: string; +}; +export type AddServiceAccountTokenCommand = { + name?: string; + secondsToLive?: number; +}; +export type AnIpMaskIsABitmaskThatCanBeUsedToManipulateIpAddressesForIpAddressingAndRouting = number[]; +export type AnIpNetRepresentsAnIpNetwork = { + IP?: string; + Mask?: AnIpMaskIsABitmaskThatCanBeUsedToManipulateIpAddressesForIpAddressingAndRouting; +}; +export type ExtKeyUsageRepresentsAnExtendedSetOfActionsThatAreValidForAGivenKey = number; +export type AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier = number[]; +export type Extension = { + Critical?: boolean; + Id?: AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier; + Value?: number[]; +}; +export type AttributeTypeAndValue = { + Type?: AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier; + Value?: any; +}; +export type Name = { + Country?: string[]; + /** ExtraNames contains attributes to be copied, raw, into any marshaled + distinguished names. Values override any attributes with the same OID. + The ExtraNames field is not populated when parsing, see Names. */ + ExtraNames?: AttributeTypeAndValue[]; + Locality?: string[]; + /** Names contains all parsed attributes. When parsing distinguished names, + this can be used to extract non-standard attributes that are not parsed + by this package. When marshaling to RDNSequences, the Names field is + ignored, see ExtraNames. */ + Names?: AttributeTypeAndValue[]; + SerialNumber?: string; + StreetAddress?: string[]; +}; +export type KeyUsage = number; +export type PolicyMappingRepresentsAPolicyMappingEntryInThePolicyMappingsExtension = { + /** IssuerDomainPolicy contains a policy OID the issuing certificate considers + equivalent to SubjectDomainPolicy in the subject certificate. */ + IssuerDomainPolicy?: string; + /** SubjectDomainPolicy contains a OID the issuing certificate considers + equivalent to IssuerDomainPolicy in the subject certificate. */ + SubjectDomainPolicy?: string; +}; +export type PublicKeyAlgorithm = number; +export type SignatureAlgorithm = number; +export type Userinfo = object; +export type AUrlRepresentsAParsedUrlTechnicallyAUriReference = { + ForceQuery?: boolean; + Fragment?: string; + Host?: string; + OmitHost?: boolean; + Opaque?: string; + Path?: string; + RawFragment?: string; + RawPath?: string; + RawQuery?: string; + Scheme?: string; + User?: Userinfo; +}; +export type ACertificateRepresentsAnX509Certificate = { + AuthorityKeyId?: number[]; + /** BasicConstraintsValid indicates whether IsCA, MaxPathLen, + and MaxPathLenZero are valid. */ + BasicConstraintsValid?: boolean; + /** CRL Distribution Points */ + CRLDistributionPoints?: string[]; + /** Subject Alternate Name values. (Note that these values may not be valid + if invalid values were contained within a parsed certificate. For + example, an element of DNSNames may not be a valid DNS domain name.) */ + DNSNames?: string[]; + EmailAddresses?: string[]; + ExcludedDNSDomains?: string[]; + ExcludedEmailAddresses?: string[]; + ExcludedIPRanges?: AnIpNetRepresentsAnIpNetwork[]; + ExcludedURIDomains?: string[]; + ExtKeyUsage?: ExtKeyUsageRepresentsAnExtendedSetOfActionsThatAreValidForAGivenKey[]; + /** Extensions contains raw X.509 extensions. When parsing certificates, + this can be used to extract non-critical extensions that are not + parsed by this package. When marshaling certificates, the Extensions + field is ignored, see ExtraExtensions. */ + Extensions?: Extension[]; + /** ExtraExtensions contains extensions to be copied, raw, into any + marshaled certificates. Values override any extensions that would + otherwise be produced based on the other fields. The ExtraExtensions + field is not populated when parsing certificates, see Extensions. */ + ExtraExtensions?: Extension[]; + IPAddresses?: string[]; + /** InhibitAnyPolicy and InhibitAnyPolicyZero indicate the presence and value + of the inhibitAnyPolicy extension. + + The value of InhibitAnyPolicy indicates the number of additional + certificates in the path after this certificate that may use the + anyPolicy policy OID to indicate a match with any other policy. + + When parsing a certificate, a positive non-zero InhibitAnyPolicy means + that the field was specified, -1 means it was unset, and + InhibitAnyPolicyZero being true mean that the field was explicitly set to + zero. The case of InhibitAnyPolicy==0 with InhibitAnyPolicyZero==false + should be treated equivalent to -1 (unset). */ + InhibitAnyPolicy?: number; + /** InhibitAnyPolicyZero indicates that InhibitAnyPolicy==0 should be + interpreted as an actual maximum path length of zero. Otherwise, that + combination is interpreted as InhibitAnyPolicy not being set. */ + InhibitAnyPolicyZero?: boolean; + /** InhibitPolicyMapping and InhibitPolicyMappingZero indicate the presence + and value of the inhibitPolicyMapping field of the policyConstraints + extension. + + The value of InhibitPolicyMapping indicates the number of additional + certificates in the path after this certificate that may use policy + mapping. + + When parsing a certificate, a positive non-zero InhibitPolicyMapping + means that the field was specified, -1 means it was unset, and + InhibitPolicyMappingZero being true mean that the field was explicitly + set to zero. The case of InhibitPolicyMapping==0 with + InhibitPolicyMappingZero==false should be treated equivalent to -1 + (unset). */ + InhibitPolicyMapping?: number; + /** InhibitPolicyMappingZero indicates that InhibitPolicyMapping==0 should be + interpreted as an actual maximum path length of zero. Otherwise, that + combination is interpreted as InhibitAnyPolicy not being set. */ + InhibitPolicyMappingZero?: boolean; + IsCA?: boolean; + Issuer?: Name; + IssuingCertificateURL?: string[]; + KeyUsage?: KeyUsage; + /** MaxPathLen and MaxPathLenZero indicate the presence and + value of the BasicConstraints' "pathLenConstraint". + + When parsing a certificate, a positive non-zero MaxPathLen + means that the field was specified, -1 means it was unset, + and MaxPathLenZero being true mean that the field was + explicitly set to zero. The case of MaxPathLen==0 with MaxPathLenZero==false + should be treated equivalent to -1 (unset). + + When generating a certificate, an unset pathLenConstraint + can be requested with either MaxPathLen == -1 or using the + zero value for both MaxPathLen and MaxPathLenZero. */ + MaxPathLen?: number; + /** MaxPathLenZero indicates that BasicConstraintsValid==true + and MaxPathLen==0 should be interpreted as an actual + maximum path length of zero. Otherwise, that combination is + interpreted as MaxPathLen not being set. */ + MaxPathLenZero?: boolean; + NotBefore?: string; + /** RFC 5280, 4.2.2.1 (Authority Information Access) */ + OCSPServer?: string[]; + PermittedDNSDomains?: string[]; + /** Name constraints */ + PermittedDNSDomainsCritical?: boolean; + PermittedEmailAddresses?: string[]; + PermittedIPRanges?: AnIpNetRepresentsAnIpNetwork[]; + PermittedURIDomains?: string[]; + /** Policies contains all policy identifiers included in the certificate. + See CreateCertificate for context about how this field and the PolicyIdentifiers field + interact. + In Go 1.22, encoding/gob cannot handle and ignores this field. */ + Policies?: string[]; + /** PolicyIdentifiers contains asn1.ObjectIdentifiers, the components + of which are limited to int32. If a certificate contains a policy which + cannot be represented by asn1.ObjectIdentifier, it will not be included in + PolicyIdentifiers, but will be present in Policies, which contains all parsed + policy OIDs. + See CreateCertificate for context about how this field and the Policies field + interact. */ + PolicyIdentifiers?: AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier[]; + /** PolicyMappings contains a list of policy mappings included in the certificate. */ + PolicyMappings?: PolicyMappingRepresentsAPolicyMappingEntryInThePolicyMappingsExtension[]; + PublicKey?: any; + PublicKeyAlgorithm?: PublicKeyAlgorithm; + Raw?: number[]; + RawIssuer?: number[]; + RawSubject?: number[]; + RawSubjectPublicKeyInfo?: number[]; + RawTBSCertificate?: number[]; + /** RequireExplicitPolicy and RequireExplicitPolicyZero indicate the presence + and value of the requireExplicitPolicy field of the policyConstraints + extension. + + The value of RequireExplicitPolicy indicates the number of additional + certificates in the path after this certificate before an explicit policy + is required for the rest of the path. When an explicit policy is required, + each subsequent certificate in the path must contain a required policy OID, + or a policy OID which has been declared as equivalent through the policy + mapping extension. + + When parsing a certificate, a positive non-zero RequireExplicitPolicy + means that the field was specified, -1 means it was unset, and + RequireExplicitPolicyZero being true mean that the field was explicitly + set to zero. The case of RequireExplicitPolicy==0 with + RequireExplicitPolicyZero==false should be treated equivalent to -1 + (unset). */ + RequireExplicitPolicy?: number; + /** RequireExplicitPolicyZero indicates that RequireExplicitPolicy==0 should be + interpreted as an actual maximum path length of zero. Otherwise, that + combination is interpreted as InhibitAnyPolicy not being set. */ + RequireExplicitPolicyZero?: boolean; + SerialNumber?: string; + Signature?: number[]; + SignatureAlgorithm?: SignatureAlgorithm; + Subject?: Name; + SubjectKeyId?: number[]; + URIs?: AUrlRepresentsAParsedUrlTechnicallyAUriReference[]; + /** UnhandledCriticalExtensions contains a list of extension IDs that + were not (fully) processed when parsing. Verify will fail if this + slice is non-empty, unless verification is delegated to an OS + library which understands all the critical extensions. + + Users can access these extensions using Extensions and can remove + elements from this slice if they believe that they have been + handled. */ + UnhandledCriticalExtensions?: AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier[]; + UnknownExtKeyUsage?: AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier[]; + Version?: number; +}; +export type JsonWebKey = { + /** Key algorithm, parsed from `alg` header. */ + Algorithm?: string; + /** X.509 certificate thumbprint (SHA-1), parsed from `x5t` header. */ + CertificateThumbprintSHA1?: number[]; + /** X.509 certificate thumbprint (SHA-256), parsed from `x5t#S256` header. */ + CertificateThumbprintSHA256?: number[]; + /** X.509 certificate chain, parsed from `x5c` header. */ + Certificates?: ACertificateRepresentsAnX509Certificate[]; + CertificatesURL?: AUrlRepresentsAParsedUrlTechnicallyAUriReference; + /** Key is the Go in-memory representation of this key. It must have one + of these types: + ed25519.PublicKey + ed25519.PrivateKey + ecdsa.PublicKey + ecdsa.PrivateKey + rsa.PublicKey + rsa.PrivateKey + []byte (a symmetric key) + + When marshaling this JSONWebKey into JSON, the "kty" header parameter + will be automatically set based on the type of this field. */ + Key?: any; + /** Key identifier, parsed from `kid` header. */ + KeyID?: string; + /** Key use, parsed from `use` header. */ + Use?: string; +}; +export type Unstructured = { + /** Object is a JSON compatible map with string, float, int, bool, []any, + or map[string]any children. */ + Object?: { + [key: string]: any; + }; +}; +export type CreateDashboardSnapshotCommand = { + /** APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + +optional */ + apiVersion?: string; + dashboard: Unstructured; + /** Unique key used to delete the snapshot. It is different from the `key` so that only the creator can delete the snapshot. Required if `external` is `true`. */ + deleteKey?: string; + /** When the snapshot should expire in seconds in seconds. Default is never to expire. */ + expires?: number; + /** these are passed when storing an external snapshot ref + Save the snapshot on an external server rather than locally. */ + external?: boolean; + /** Define the unique key. Required if `external` is `true`. */ + key?: string; + /** Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + +optional */ + kind?: string; + /** Snapshot name */ + name?: string; +}; +export type CreateTeamCommand = { + email?: string; + name?: string; +}; +export type TeamDto = { + accessControl?: { + [key: string]: boolean; + }; + avatarUrl?: string; + email?: string; + externalUID?: string; + id?: number; + isProvisioned?: boolean; + memberCount?: number; + name?: string; + orgId?: number; + permission?: PermissionType; + uid?: string; +}; +export type SearchTeamQueryResult = { + page?: number; + perPage?: number; + teams?: TeamDto[]; + totalCount?: number; +}; +export type TeamGroupDto = { + groupId?: string; + orgId?: number; + teamId?: number; +}; +export type TeamGroupMapping = { + groupId?: string; +}; +export type SearchTeamGroupsQueryResult = { + page?: number; + perPage?: number; + teamGroups?: TeamGroupDto[]; + totalCount?: number; +}; +export type UpdateTeamCommand = { + Email?: string; + ExternalUID?: string; + ID?: number; + Name?: string; +}; +export type TeamMemberDto = { + auth_module?: string; + avatarUrl?: string; + email?: string; + labels?: string[]; + login?: string; + name?: string; + orgId?: number; + permission?: PermissionType; + teamId?: number; + teamUID?: string; + uid?: string; + userId?: number; + userUID?: string; +}; +export type AddTeamMemberCommand = { + userId?: number; +}; +export type SetTeamMembershipsCommand = { + admins?: string[]; + members?: string[]; +}; +export type UpdateTeamMemberCommand = { + permission?: PermissionType; +}; +export type UserProfileDto = { + accessControl?: { + [key: string]: boolean; + }; + authLabels?: string[]; + avatarUrl?: string; + createdAt?: string; + email?: string; + id?: number; + isDisabled?: boolean; + isExternal?: boolean; + isExternallySynced?: boolean; + isGrafanaAdmin?: boolean; + isGrafanaAdminExternallySynced?: boolean; + isProvisioned?: boolean; + login?: string; + name?: string; + orgId?: number; + theme?: string; + uid?: string; + updatedAt?: string; +}; +export type UpdateUserCommand = { + email?: string; + login?: string; + name?: string; + theme?: string; +}; +export type UserOrgDto = { + name?: string; + orgId?: number; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; +}; +export type ChangeUserPasswordCommand = { + newPassword?: Password; + oldPassword?: Password; +}; +export type UserSearchHitDto = { + authLabels?: string[]; + avatarUrl?: string; + email?: string; + id?: number; + isAdmin?: boolean; + isDisabled?: boolean; + isProvisioned?: boolean; + lastSeenAt?: string; + lastSeenAtAge?: string; + login?: string; + name?: string; + uid?: string; +}; +export type SearchUserQueryResult = { + page?: number; + perPage?: number; + totalCount?: number; + users?: UserSearchHitDto[]; +}; +export type RelativeTimeRange = { + from?: Duration; + to?: Duration; +}; +export type AlertQueryRepresentsASingleQueryAssociatedWithAnAlertDefinition = { + /** Grafana data source unique identifier; it should be '__expr__' for a Server Side Expression operation. */ + datasourceUid?: string; + /** JSON is the raw JSON query and includes the above properties as well as custom properties. */ + model?: object; + /** QueryType is an optional identifier for the type of query. + It can be used to distinguish different types of queries. */ + queryType?: string; + /** RefID is the unique identifier of the query, set by the frontend call. */ + refId?: string; + relativeTimeRange?: RelativeTimeRange; +}; +export type AlertRuleNotificationSettings = { + /** Override the times when notifications should not be muted. These must match the name of a mute time interval defined + in the alertmanager configuration time_intervals section. All notifications will be suppressed unless they are sent + at the time that matches any interval. */ + active_time_intervals?: string[]; + /** Override the labels by which incoming alerts are grouped together. For example, multiple alerts coming in for + cluster=A and alertname=LatencyHigh would be batched into a single group. To aggregate by all possible labels + use the special value '...' as the sole label name. + This effectively disables aggregation entirely, passing through all alerts as-is. This is unlikely to be what + you want, unless you have a very low alert volume or your upstream notification system performs its own grouping. + Must include 'alertname' and 'grafana_folder' if not using '...'. */ + group_by?: string[]; + /** Override how long to wait before sending a notification about new alerts that are added to a group of alerts for + which an initial notification has already been sent. (Usually ~5m or more.) */ + group_interval?: string; + /** Override how long to initially wait to send a notification for a group of alerts. Allows to wait for an + inhibiting alert to arrive or collect more initial alerts for the same group. (Usually ~0s to few minutes.) */ + group_wait?: string; + /** Override the times when notifications should be muted. These must match the name of a mute time interval defined + in the alertmanager configuration time_intervals section. When muted it will not send any notifications, but + otherwise acts normally. */ + mute_time_intervals?: string[]; + /** Name of the receiver to send notifications to. */ + receiver: string; + /** Override how long to wait before sending a notification again if it has already been sent successfully for an + alert. (Usually ~3h or more). + Note that this parameter is implicitly bound by Alertmanager's `--data.retention` configuration flag. + Notifications will be resent after either repeat_interval or the data retention period have passed, whichever + occurs first. `repeat_interval` should not be less than `group_interval`. */ + repeat_interval?: string; +}; +export type Provenance = string; +export type Record = { + /** Which expression node should be used as the input for the recorded metric. */ + from: string; + /** Name of the recorded metric. */ + metric: string; + /** Which data source should be used to write the output of the recording rule, specified by UID. */ + target_datasource_uid?: string; +}; +export type ProvisionedAlertRule = { + annotations?: { + [key: string]: string; + }; + condition: string; + data: AlertQueryRepresentsASingleQueryAssociatedWithAnAlertDefinition[]; + execErrState: 'OK' | 'Alerting' | 'Error'; + folderUID: string; + for: string; + id?: number; + isPaused?: boolean; + keep_firing_for?: string; + labels?: { + [key: string]: string; + }; + missingSeriesEvalsToResolve?: number; + noDataState: 'Alerting' | 'NoData' | 'OK'; + notification_settings?: AlertRuleNotificationSettings; + orgID: number; + provenance?: Provenance; + record?: Record; + ruleGroup: string; + title: string; + uid?: string; +}; +export type ProvisionedAlertRuleRead = { + annotations?: { + [key: string]: string; + }; + condition: string; + data: AlertQueryRepresentsASingleQueryAssociatedWithAnAlertDefinition[]; + execErrState: 'OK' | 'Alerting' | 'Error'; + folderUID: string; + for: string; + id?: number; + isPaused?: boolean; + keep_firing_for?: string; + labels?: { + [key: string]: string; + }; + missingSeriesEvalsToResolve?: number; + noDataState: 'Alerting' | 'NoData' | 'OK'; + notification_settings?: AlertRuleNotificationSettings; + orgID: number; + provenance?: Provenance; + record?: Record; + ruleGroup: string; + title: string; + uid?: string; + updated?: string; +}; +export type ProvisionedAlertRules = ProvisionedAlertRule[]; +export type ProvisionedAlertRulesRead = ProvisionedAlertRuleRead[]; +export type ValidationError = { + message?: string; +}; +export type RawMessage = object; +export type ReceiverExportIsTheProvisionedFileExportOfAlertingReceiverV1 = { + disableResolveMessage?: boolean; + settings?: RawMessage; + type?: string; + uid?: string; +}; +export type ContactPointExportIsTheProvisionedFileExportOfAlertingContactPointV1 = { + name?: string; + orgId?: number; + receivers?: ReceiverExportIsTheProvisionedFileExportOfAlertingReceiverV1[]; +}; +export type RelativeTimeRangeExport = { + from?: number; + to?: number; +}; +export type AlertQueryExportIsTheProvisionedExportOfModelsAlertQuery = { + datasourceUid?: string; + model?: { + [key: string]: any; + }; + queryType?: string; + refId?: string; + relativeTimeRange?: RelativeTimeRangeExport; +}; +export type AlertRuleNotificationSettingsExportIsTheProvisionedExportOfModelsNotificationSettings = { + active_time_intervals?: string[]; + group_by?: string[]; + group_interval?: string; + group_wait?: string; + mute_time_intervals?: string[]; + receiver?: string; + repeat_interval?: string; +}; +export type RecordIsTheProvisionedExportOfModelsRecord = { + from?: string; + metric?: string; + targetDatasourceUid?: string; +}; +export type AlertRuleExportIsTheProvisionedFileExportOfModelsAlertRule = { + annotations?: { + [key: string]: string; + }; + condition?: string; + dashboardUid?: string; + data?: AlertQueryExportIsTheProvisionedExportOfModelsAlertQuery[]; + execErrState?: 'OK' | 'Alerting' | 'Error'; + for?: Duration; + isPaused?: boolean; + keepFiringFor?: Duration; + labels?: { + [key: string]: string; + }; + missing_series_evals_to_resolve?: number; + noDataState?: 'Alerting' | 'NoData' | 'OK'; + notification_settings?: AlertRuleNotificationSettingsExportIsTheProvisionedExportOfModelsNotificationSettings; + panelId?: number; + record?: RecordIsTheProvisionedExportOfModelsRecord; + title?: string; + uid?: string; +}; +export type AlertRuleGroupExportIsTheProvisionedFileExportOfAlertRuleGroupV1 = { + folder?: string; + interval?: Duration; + name?: string; + orgId?: number; + rules?: AlertRuleExportIsTheProvisionedFileExportOfModelsAlertRule[]; +}; +export type TimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted = { + name?: string; + time_intervals?: TimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted[]; +}; +export type MuteTimeIntervalExport = { + name?: string; + orgId?: number; + time_intervals?: TimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted[]; +}; +export type MatchRegexpsRepresentsAMapOfRegexp = { + [key: string]: string; +}; +export type MatchTypeIsAnEnumForLabelMatchingTypes = number; +export type MatcherModelsTheMatchingOfALabel = { + Name?: string; + Type?: MatchTypeIsAnEnumForLabelMatchingTypes; + Value?: string; +}; +export type Matchers = MatcherModelsTheMatchingOfALabel[]; +export type ObjectMatcherIsAMatcherThatCanBeUsedToFilterAlerts = string[]; +export type ObjectMatchersIsAListOfMatchersThatCanBeUsedToFilterAlerts = + ObjectMatcherIsAMatcherThatCanBeUsedToFilterAlerts[]; +export type RouteExport = { + active_time_intervals?: string[]; + continue?: boolean; + group_by?: string[]; + group_interval?: string; + group_wait?: string; + /** Deprecated. Remove before v1.0 release. */ + match?: { + [key: string]: string; + }; + match_re?: MatchRegexpsRepresentsAMapOfRegexp; + matchers?: Matchers; + mute_time_intervals?: string[]; + object_matchers?: ObjectMatchersIsAListOfMatchersThatCanBeUsedToFilterAlerts; + receiver?: string; + repeat_interval?: string; + routes?: RouteExport[]; +}; +export type NotificationPolicyExportIsTheProvisionedFileExportOfAlertingNotificiationPolicyV1 = { + active_time_intervals?: string[]; + continue?: boolean; + group_by?: string[]; + group_interval?: string; + group_wait?: string; + /** Deprecated. Remove before v1.0 release. */ + match?: { + [key: string]: string; + }; + match_re?: MatchRegexpsRepresentsAMapOfRegexp; + matchers?: Matchers; + mute_time_intervals?: string[]; + object_matchers?: ObjectMatchersIsAListOfMatchersThatCanBeUsedToFilterAlerts; + orgId?: number; + receiver?: string; + repeat_interval?: string; + routes?: RouteExport[]; +}; +export type AlertingFileExportIsTheFullProvisionedFileExport = { + apiVersion?: number; + contactPoints?: ContactPointExportIsTheProvisionedFileExportOfAlertingContactPointV1[]; + groups?: AlertRuleGroupExportIsTheProvisionedFileExportOfAlertRuleGroupV1[]; + muteTimes?: MuteTimeIntervalExport[]; + policies?: NotificationPolicyExportIsTheProvisionedFileExportOfAlertingNotificiationPolicyV1[]; +}; +export type EmbeddedContactPoint = { + disableResolveMessage?: boolean; + /** Name is used as grouping key in the UI. Contact points with the + same name will be grouped in the UI. */ + name?: string; + settings: Json; + type: + | 'alertmanager' + | 'dingding' + | 'discord' + | 'email' + | 'googlechat' + | 'kafka' + | 'line' + | 'opsgenie' + | 'pagerduty' + | 'pushover' + | 'sensugo' + | 'slack' + | 'teams' + | 'telegram' + | 'threema' + | 'victorops' + | 'webhook' + | 'wecom'; + /** UID is the unique identifier of the contact point. The UID can be + set by the user. */ + uid?: string; +}; +export type EmbeddedContactPointRead = { + disableResolveMessage?: boolean; + /** Name is used as grouping key in the UI. Contact points with the + same name will be grouped in the UI. */ + name?: string; + provenance?: string; + settings: Json; + type: + | 'alertmanager' + | 'dingding' + | 'discord' + | 'email' + | 'googlechat' + | 'kafka' + | 'line' + | 'opsgenie' + | 'pagerduty' + | 'pushover' + | 'sensugo' + | 'slack' + | 'teams' + | 'telegram' + | 'threema' + | 'victorops' + | 'webhook' + | 'wecom'; + /** UID is the unique identifier of the contact point. The UID can be + set by the user. */ + uid?: string; +}; +export type ContactPoints = EmbeddedContactPoint[]; +export type ContactPointsRead = EmbeddedContactPointRead[]; +export type PermissionDenied = object; +export type Ack = object; +export type NotFound = object; +export type AlertRuleGroup = { + folderUid?: string; + interval?: number; + rules?: ProvisionedAlertRule[]; + title?: string; +}; +export type AlertRuleGroupRead = { + folderUid?: string; + interval?: number; + rules?: ProvisionedAlertRuleRead[]; + title?: string; +}; +export type MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted = { + name?: string; + time_intervals?: TimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted[]; +}; +export type MuteTimings = MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted[]; +export type Route = { + active_time_intervals?: string[]; + continue?: boolean; + group_by?: string[]; + group_interval?: string; + group_wait?: string; + /** Deprecated. Remove before v1.0 release. */ + match?: { + [key: string]: string; + }; + match_re?: MatchRegexpsRepresentsAMapOfRegexp; + matchers?: Matchers; + mute_time_intervals?: string[]; + object_matchers?: ObjectMatchersIsAListOfMatchersThatCanBeUsedToFilterAlerts; + provenance?: Provenance; + receiver?: string; + repeat_interval?: string; + routes?: Route[]; +}; +export type NotificationTemplate = { + name?: string; + provenance?: Provenance; + template?: string; + version?: string; +}; +export type NotificationTemplates = NotificationTemplate[]; +export type NotificationTemplateContent = { + template?: string; + version?: string; +}; +export const { + useSearchResultMutation, + useListRolesQuery, + useCreateRoleMutation, + useDeleteRoleMutation, + useGetRoleQuery, + useUpdateRoleMutation, + useGetRoleAssignmentsQuery, + useSetRoleAssignmentsMutation, + useGetAccessControlStatusQuery, + useListTeamsRolesMutation, + useListTeamRolesQuery, + useAddTeamRoleMutation, + useSetTeamRolesMutation, + useRemoveTeamRoleMutation, + useListUsersRolesMutation, + useListUserRolesQuery, + useAddUserRoleMutation, + useSetUserRolesMutation, + useRemoveUserRoleMutation, + useGetResourceDescriptionQuery, + useGetResourcePermissionsQuery, + useSetResourcePermissionsMutation, + useSetResourcePermissionsForBuiltInRoleMutation, + useSetResourcePermissionsForTeamMutation, + useSetResourcePermissionsForUserMutation, + useGetSyncStatusQuery, + useReloadLdapCfgMutation, + useGetLdapStatusQuery, + usePostSyncUserWithLdapMutation, + useGetUserFromLdapQuery, + useAdminProvisioningReloadAccessControlMutation, + useAdminProvisioningReloadDashboardsMutation, + useAdminProvisioningReloadDatasourcesMutation, + useAdminProvisioningReloadPluginsMutation, + useAdminGetSettingsQuery, + useAdminGetStatsQuery, + useAdminCreateUserMutation, + useAdminDeleteUserMutation, + useAdminGetUserAuthTokensQuery, + useAdminDisableUserMutation, + useAdminEnableUserMutation, + useAdminLogoutUserMutation, + useAdminUpdateUserPasswordMutation, + useAdminUpdateUserPermissionsMutation, + useGetUserQuotaQuery, + useUpdateUserQuotaMutation, + useAdminRevokeUserAuthTokenMutation, + useGetAnnotationsQuery, + usePostAnnotationMutation, + usePostGraphiteAnnotationMutation, + useMassDeleteAnnotationsMutation, + useGetAnnotationTagsQuery, + useDeleteAnnotationByIdMutation, + useGetAnnotationByIdQuery, + usePatchAnnotationMutation, + useUpdateAnnotationMutation, + useListDevicesQuery, + useSearchDevicesQuery, + useGetSessionListQuery, + useCreateSessionMutation, + useDeleteSessionMutation, + useGetSessionQuery, + useCreateSnapshotMutation, + useGetSnapshotQuery, + useCancelSnapshotMutation, + useUploadSnapshotMutation, + useGetShapshotListQuery, + useGetResourceDependenciesQuery, + useGetCloudMigrationTokenQuery, + useCreateCloudMigrationTokenMutation, + useDeleteCloudMigrationTokenMutation, + useRouteConvertPrometheusCortexGetRulesQuery, + useRouteConvertPrometheusCortexPostRuleGroupsMutation, + useRouteConvertPrometheusCortexDeleteNamespaceMutation, + useRouteConvertPrometheusCortexGetNamespaceQuery, + useRouteConvertPrometheusCortexPostRuleGroupMutation, + useRouteConvertPrometheusCortexDeleteRuleGroupMutation, + useRouteConvertPrometheusCortexGetRuleGroupQuery, + useRouteConvertPrometheusGetRulesQuery, + useRouteConvertPrometheusPostRuleGroupsMutation, + useRouteConvertPrometheusDeleteNamespaceMutation, + useRouteConvertPrometheusGetNamespaceQuery, + useRouteConvertPrometheusPostRuleGroupMutation, + useRouteConvertPrometheusDeleteRuleGroupMutation, + useRouteConvertPrometheusGetRuleGroupQuery, + useSearchDashboardSnapshotsQuery, + useCalculateDashboardDiffMutation, + usePostDashboardMutation, + useGetHomeDashboardQuery, + useImportDashboardMutation, + useInterpolateDashboardMutation, + useListPublicDashboardsQuery, + useGetDashboardTagsQuery, + useGetPublicDashboardQuery, + useCreatePublicDashboardMutation, + useDeletePublicDashboardMutation, + useUpdatePublicDashboardMutation, + useDeleteDashboardByUidMutation, + useGetDashboardByUidQuery, + useGetDashboardPermissionsListByUidQuery, + useUpdateDashboardPermissionsByUidMutation, + useRestoreDashboardVersionByUidMutation, + useGetDashboardVersionsByUidQuery, + useGetDashboardVersionByUidQuery, + useGetDataSourcesQuery, + useAddDataSourceMutation, + useGetCorrelationsQuery, + useGetDataSourceIdByNameQuery, + useDeleteDataSourceByNameMutation, + useGetDataSourceByNameQuery, + useDatasourceProxyDeleteByUiDcallsMutation, + useDatasourceProxyGetByUiDcallsQuery, + useDatasourceProxyPostByUiDcallsMutation, + useGetCorrelationsBySourceUidQuery, + useCreateCorrelationMutation, + useGetCorrelationQuery, + useUpdateCorrelationMutation, + useDeleteDataSourceByUidMutation, + useGetDataSourceByUidQuery, + useUpdateDataSourceByUidMutation, + useDeleteCorrelationMutation, + useCheckDatasourceHealthWithUidQuery, + useGetTeamLbacRulesApiQuery, + useUpdateTeamLbacRulesApiMutation, + useCallDatasourceResourceWithUidQuery, + useGetDataSourceCacheConfigQuery, + useSetDataSourceCacheConfigMutation, + useCleanDataSourceCacheMutation, + useDisableDataSourceCacheMutation, + useEnableDataSourceCacheMutation, + useQueryMetricsWithExpressionsMutation, + useGetFoldersQuery, + useCreateFolderMutation, + useDeleteFolderMutation, + useGetFolderByUidQuery, + useUpdateFolderMutation, + useGetFolderDescendantCountsQuery, + useMoveFolderMutation, + useGetFolderPermissionListQuery, + useUpdateFolderPermissionsMutation, + useGetMappedGroupsQuery, + useDeleteGroupMappingsMutation, + useCreateGroupMappingsMutation, + useUpdateGroupMappingsMutation, + useGetGroupRolesQuery, + useGetHealthQuery, + useGetLibraryElementsQuery, + useCreateLibraryElementMutation, + useGetLibraryElementByNameQuery, + useDeleteLibraryElementByUidMutation, + useGetLibraryElementByUidQuery, + useUpdateLibraryElementMutation, + useGetLibraryElementConnectionsQuery, + useGetStatusQuery, + useRefreshLicenseStatsQuery, + useDeleteLicenseTokenMutation, + useGetLicenseTokenQuery, + usePostLicenseTokenMutation, + usePostRenewLicenseTokenMutation, + useGetSamlLogoutQuery, + useGetCurrentOrgQuery, + useUpdateCurrentOrgMutation, + useUpdateCurrentOrgAddressMutation, + useGetPendingOrgInvitesQuery, + useAddOrgInviteMutation, + useRevokeInviteMutation, + useGetOrgPreferencesQuery, + usePatchOrgPreferencesMutation, + useUpdateOrgPreferencesMutation, + useGetCurrentOrgQuotaQuery, + useGetOrgUsersForCurrentOrgQuery, + useAddOrgUserToCurrentOrgMutation, + useGetOrgUsersForCurrentOrgLookupQuery, + useRemoveOrgUserForCurrentOrgMutation, + useUpdateOrgUserForCurrentOrgMutation, + useSearchOrgsQuery, + useCreateOrgMutation, + useGetOrgByNameQuery, + useDeleteOrgByIdMutation, + useGetOrgByIdQuery, + useUpdateOrgMutation, + useUpdateOrgAddressMutation, + useGetOrgQuotaQuery, + useUpdateOrgQuotaMutation, + useGetOrgUsersQuery, + useAddOrgUserMutation, + useSearchOrgUsersQuery, + useRemoveOrgUserMutation, + useUpdateOrgUserMutation, + useSearchPlaylistsQuery, + useCreatePlaylistMutation, + useDeletePlaylistMutation, + useGetPlaylistQuery, + useUpdatePlaylistMutation, + useGetPlaylistItemsQuery, + useViewPublicDashboardQuery, + useGetPublicAnnotationsQuery, + useQueryPublicDashboardMutation, + useSearchQueriesQuery, + useCreateQueryMutation, + useUnstarQueryMutation, + useStarQueryMutation, + useDeleteQueryMutation, + usePatchQueryCommentMutation, + useListRecordingRulesQuery, + useCreateRecordingRuleMutation, + useUpdateRecordingRuleMutation, + useTestCreateRecordingRuleMutation, + useDeleteRecordingRuleWriteTargetMutation, + useGetRecordingRuleWriteTargetQuery, + useCreateRecordingRuleWriteTargetMutation, + useDeleteRecordingRuleMutation, + useGetReportsQuery, + useCreateReportMutation, + useGetReportsByDashboardUidQuery, + useSendReportMutation, + useGetSettingsImageQuery, + useRenderReportCsVsQuery, + useRenderReportPdFsQuery, + useGetReportSettingsQuery, + useSaveReportSettingsMutation, + useSendTestEmailMutation, + usePostAcsMutation, + useGetMetadataQuery, + useGetSloQuery, + usePostSloMutation, + useSearchQuery, + useListSortOptionsQuery, + useCreateServiceAccountMutation, + useSearchOrgServiceAccountsWithPagingQuery, + useDeleteServiceAccountMutation, + useRetrieveServiceAccountQuery, + useUpdateServiceAccountMutation, + useListTokensQuery, + useCreateTokenMutation, + useDeleteTokenMutation, + useRetrieveJwksQuery, + useGetSharingOptionsQuery, + useCreateDashboardSnapshotMutation, + useDeleteDashboardSnapshotByDeleteKeyQuery, + useDeleteDashboardSnapshotMutation, + useGetDashboardSnapshotQuery, + useCreateTeamMutation, + useSearchTeamsQuery, + useRemoveTeamGroupApiQueryMutation, + useGetTeamGroupsApiQuery, + useAddTeamGroupApiMutation, + useSearchTeamGroupsQuery, + useDeleteTeamByIdMutation, + useGetTeamByIdQuery, + useUpdateTeamMutation, + useGetTeamMembersQuery, + useAddTeamMemberMutation, + useSetTeamMembershipsMutation, + useRemoveTeamMemberMutation, + useUpdateTeamMemberMutation, + useGetTeamPreferencesQuery, + useUpdateTeamPreferencesMutation, + useGetSignedInUserQuery, + useUpdateSignedInUserMutation, + useGetUserAuthTokensQuery, + useUpdateUserEmailQuery, + useClearHelpFlagsQuery, + useSetHelpFlagMutation, + useGetSignedInUserOrgListQuery, + useChangeUserPasswordMutation, + useGetUserPreferencesQuery, + usePatchUserPreferencesMutation, + useUpdateUserPreferencesMutation, + useGetUserQuotasQuery, + useRevokeUserAuthTokenMutation, + useUnstarDashboardByUidMutation, + useStarDashboardByUidMutation, + useGetSignedInUserTeamListQuery, + useUserSetUsingOrgMutation, + useSearchUsersQuery, + useGetUserByLoginOrEmailQuery, + useSearchUsersWithPagingQuery, + useGetUserByIdQuery, + useUpdateUserMutation, + useGetUserOrgListQuery, + useGetUserTeamsQuery, + useRouteGetAlertRulesQuery, + useRoutePostAlertRuleMutation, + useRouteGetAlertRulesExportQuery, + useRouteDeleteAlertRuleMutation, + useRouteGetAlertRuleQuery, + useRoutePutAlertRuleMutation, + useRouteGetAlertRuleExportQuery, + useRouteGetContactpointsQuery, + useRoutePostContactpointsMutation, + useRouteGetContactpointsExportQuery, + useRouteDeleteContactpointsMutation, + useRoutePutContactpointMutation, + useRouteDeleteAlertRuleGroupMutation, + useRouteGetAlertRuleGroupQuery, + useRoutePutAlertRuleGroupMutation, + useRouteGetAlertRuleGroupExportQuery, + useRouteGetMuteTimingsQuery, + useRoutePostMuteTimingMutation, + useRouteExportMuteTimingsQuery, + useRouteDeleteMuteTimingMutation, + useRouteGetMuteTimingQuery, + useRoutePutMuteTimingMutation, + useRouteExportMuteTimingQuery, + useRouteResetPolicyTreeMutation, + useRouteGetPolicyTreeQuery, + useRoutePutPolicyTreeMutation, + useRouteGetPolicyTreeExportQuery, + useRouteGetTemplatesQuery, + useRouteDeleteTemplateMutation, + useRouteGetTemplateQuery, + useRoutePutTemplateMutation, + useListAllProvidersSettingsQuery, + useRemoveProviderSettingsMutation, + useGetProviderSettingsQuery, + useUpdateProviderSettingsMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/legacy/index.ts b/packages/grafana-api-clients/src/clients/rtkq/legacy/index.ts new file mode 100644 index 00000000000..722532483b6 --- /dev/null +++ b/packages/grafana-api-clients/src/clients/rtkq/legacy/index.ts @@ -0,0 +1,5 @@ +import { generatedAPI as rawAPI } from './endpoints.gen'; + +export const legacyAPI = rawAPI.enhanceEndpoints({}); + +export * from './endpoints.gen'; diff --git a/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts index 7a42a89de07..51bf82c23c6 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts @@ -1,11 +1,15 @@ import { api } from './baseAPI'; -export const addTagTypes = ['Playlist'] as const; +export const addTagTypes = ['API Discovery', 'Playlist'] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, }) .injectEndpoints({ endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/playlist.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), listPlaylist: build.query({ query: (queryArg) => ({ url: `/playlists`, @@ -39,6 +43,29 @@ const injectedRtkApi = api }), invalidatesTags: ['Playlist'], }), + deletecollectionPlaylist: build.mutation({ + query: (queryArg) => ({ + url: `/playlists`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['Playlist'], + }), getPlaylist: build.query({ query: (queryArg) => ({ url: `/playlists/${queryArg.name}`, @@ -77,10 +104,65 @@ const injectedRtkApi = api }), invalidatesTags: ['Playlist'], }), + updatePlaylist: build.mutation({ + query: (queryArg) => ({ + url: `/playlists/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Playlist'], + }), + getPlaylistStatus: build.query({ + query: (queryArg) => ({ + url: `/playlists/${queryArg.name}/status`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Playlist'], + }), + replacePlaylistStatus: build.mutation({ + query: (queryArg) => ({ + url: `/playlists/${queryArg.name}/status`, + method: 'PUT', + body: queryArg.playlist, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Playlist'], + }), + updatePlaylistStatus: build.mutation({ + query: (queryArg) => ({ + url: `/playlists/${queryArg.name}/status`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Playlist'], + }), }), overrideExisting: false, }); export { injectedRtkApi as generatedAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList; +export type GetApiResourcesApiArg = void; export type ListPlaylistApiResponse = /** status 200 OK */ PlaylistList; export type ListPlaylistApiArg = { /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ @@ -141,6 +223,57 @@ export type CreatePlaylistApiArg = { fieldValidation?: string; playlist: Playlist; }; +export type DeletecollectionPlaylistApiResponse = /** status 200 OK */ Status; +export type DeletecollectionPlaylistApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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". + + This 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. */ + continue?: string; + /** 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 */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** 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. */ + gracePeriodSeconds?: number; + /** 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 */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** 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. + + The 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. */ + limit?: number; + /** 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. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** 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. + + Defaults to unset */ + resourceVersion?: string; + /** 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. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `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. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; export type GetPlaylistApiResponse = /** status 200 OK */ Playlist; export type GetPlaylistApiArg = { /** name of the Playlist */ @@ -179,6 +312,91 @@ export type DeletePlaylistApiArg = { /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ propagationPolicy?: string; }; +export type UpdatePlaylistApiResponse = /** status 200 OK */ Playlist | /** status 201 Created */ Playlist; +export type UpdatePlaylistApiArg = { + /** name of the Playlist */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; +export type GetPlaylistStatusApiResponse = /** status 200 OK */ Playlist; +export type GetPlaylistStatusApiArg = { + /** name of the Playlist */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplacePlaylistStatusApiResponse = /** status 200 OK */ Playlist | /** status 201 Created */ Playlist; +export type ReplacePlaylistStatusApiArg = { + /** name of the Playlist */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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. */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + playlist: Playlist; +}; +export type UpdatePlaylistStatusApiResponse = /** status 200 OK */ Playlist | /** status 201 Created */ Playlist; +export type UpdatePlaylistStatusApiArg = { + /** name of the Playlist */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; +export type ApiResource = { + /** categories is a list of the grouped resources this resource belongs to (e.g. 'all') */ + categories?: string[]; + /** group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale". */ + group?: string; + /** kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') */ + kind: string; + /** name is the plural name of the resource. */ + name: string; + /** namespaced indicates if a resource is namespaced or not. */ + namespaced: boolean; + /** shortNames is a list of suggested short names of the resource. */ + shortNames?: string[]; + /** singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface. */ + singularName: string; + /** The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates. */ + storageVersionHash?: string; + /** verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy) */ + verbs: string[]; + /** version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". */ + version?: string; +}; +export type ApiResourceList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** groupVersion is the group and version this APIResourceList is for. */ + groupVersion: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** resources contains the name of the resources and if they are namespaced. */ + resources: ApiResource[]; +}; export type Time = string; export type FieldsV1 = object; export type ManagedFieldsEntry = { @@ -377,3 +595,17 @@ export type Status = { /** Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status */ status?: string; }; +export type Patch = object; +export const { + useGetApiResourcesQuery, + useListPlaylistQuery, + useCreatePlaylistMutation, + useDeletecollectionPlaylistMutation, + useGetPlaylistQuery, + useReplacePlaylistMutation, + useDeletePlaylistMutation, + useUpdatePlaylistMutation, + useGetPlaylistStatusQuery, + useReplacePlaylistStatusMutation, + useUpdatePlaylistStatusMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts index 41e4cac3c01..7d566227d1a 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts @@ -1,11 +1,15 @@ import { api } from './baseAPI'; -export const addTagTypes = ['Job', 'Repository', 'Provisioning'] as const; +export const addTagTypes = ['API Discovery', 'Job', 'Repository', 'Provisioning'] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, }) .injectEndpoints({ endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/provisioning.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), listJob: build.query({ query: (queryArg) => ({ url: `/jobs`, @@ -100,6 +104,21 @@ const injectedRtkApi = api }), invalidatesTags: ['Job'], }), + updateJob: build.mutation({ + query: (queryArg) => ({ + url: `/jobs/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Job'], + }), listRepository: build.query({ query: (queryArg) => ({ url: `/repositories`, @@ -197,6 +216,21 @@ const injectedRtkApi = api }), invalidatesTags: ['Repository'], }), + updateRepository: build.mutation({ + query: (queryArg) => ({ + url: `/repositories/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Repository'], + }), getRepositoryFiles: build.query({ query: (queryArg) => ({ url: `/repositories/${queryArg.name}/files/`, @@ -336,6 +370,21 @@ const injectedRtkApi = api }), invalidatesTags: ['Repository'], }), + updateRepositoryStatus: build.mutation({ + query: (queryArg) => ({ + url: `/repositories/${queryArg.name}/status`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Repository'], + }), createRepositoryTest: build.mutation({ query: (queryArg) => ({ url: `/repositories/${queryArg.name}/test`, method: 'POST', body: queryArg.body }), invalidatesTags: ['Repository'], @@ -360,6 +409,8 @@ const injectedRtkApi = api overrideExisting: false, }); export { injectedRtkApi as generatedAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList; +export type GetApiResourcesApiArg = void; export type ListJobApiResponse = /** status 200 OK */ JobList; export type ListJobApiArg = { /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ @@ -509,6 +560,22 @@ export type DeleteJobApiArg = { /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ propagationPolicy?: string; }; +export type UpdateJobApiResponse = /** status 200 OK */ Job | /** status 201 Created */ Job; +export type UpdateJobApiArg = { + /** name of the Job */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; export type ListRepositoryApiResponse = /** status 200 OK */ RepositoryList; export type ListRepositoryApiArg = { /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ @@ -658,6 +725,22 @@ export type DeleteRepositoryApiArg = { /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ propagationPolicy?: string; }; +export type UpdateRepositoryApiResponse = /** status 200 OK */ Repository | /** status 201 Created */ Repository; +export type UpdateRepositoryApiArg = { + /** name of the Repository */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; export type GetRepositoryFilesApiResponse = /** status 200 OK */ { /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ apiVersion?: string; @@ -811,6 +894,22 @@ export type ReplaceRepositoryStatusApiArg = { fieldValidation?: string; repository: Repository; }; +export type UpdateRepositoryStatusApiResponse = /** status 200 OK */ Repository | /** status 201 Created */ Repository; +export type UpdateRepositoryStatusApiArg = { + /** name of the Repository */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** 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 */ + dryRun?: string; + /** 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). */ + fieldManager?: string; + /** 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. */ + fieldValidation?: string; + /** 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. */ + force?: boolean; + patch: Patch; +}; export type CreateRepositoryTestApiResponse = /** status 200 OK */ TestResults; export type CreateRepositoryTestApiArg = { /** name of the TestResults */ @@ -840,6 +939,38 @@ export type GetFrontendSettingsApiResponse = /** status 200 undefined */ Reposit export type GetFrontendSettingsApiArg = void; export type GetResourceStatsApiResponse = /** status 200 undefined */ ResourceStats; export type GetResourceStatsApiArg = void; +export type ApiResource = { + /** categories is a list of the grouped resources this resource belongs to (e.g. 'all') */ + categories?: string[]; + /** group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale". */ + group?: string; + /** kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') */ + kind: string; + /** name is the plural name of the resource. */ + name: string; + /** namespaced indicates if a resource is namespaced or not. */ + namespaced: boolean; + /** shortNames is a list of suggested short names of the resource. */ + shortNames?: string[]; + /** singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface. */ + singularName: string; + /** The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates. */ + storageVersionHash?: string; + /** verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy) */ + verbs: string[]; + /** version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". */ + version?: string; +}; +export type ApiResourceList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** groupVersion is the group and version this APIResourceList is for. */ + groupVersion: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** resources contains the name of the resources and if they are namespaced. */ + resources: ApiResource[]; +}; export type Time = string; export type FieldsV1 = object; export type ManagedFieldsEntry = { @@ -1117,6 +1248,7 @@ export type Status = { /** Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status */ status?: string; }; +export type Patch = object; export type InlineSecureValue = | { /** Create a secure value -- this is only used for POST/PUT */ @@ -1506,18 +1638,21 @@ export type ResourceStats = { unmanaged?: ResourceCount[]; }; export const { + useGetApiResourcesQuery, useListJobQuery, useCreateJobMutation, useDeletecollectionJobMutation, useGetJobQuery, useReplaceJobMutation, useDeleteJobMutation, + useUpdateJobMutation, useListRepositoryQuery, useCreateRepositoryMutation, useDeletecollectionRepositoryMutation, useGetRepositoryQuery, useReplaceRepositoryMutation, useDeleteRepositoryMutation, + useUpdateRepositoryMutation, useGetRepositoryFilesQuery, useGetRepositoryFilesWithPathQuery, useReplaceRepositoryFilesWithPathMutation, @@ -1533,6 +1668,7 @@ export const { useGetRepositoryResourcesQuery, useGetRepositoryStatusQuery, useReplaceRepositoryStatusMutation, + useUpdateRepositoryStatusMutation, useCreateRepositoryTestMutation, useGetRepositoryWebhookQuery, useCreateRepositoryWebhookMutation, diff --git a/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts index 9cabcb8ab7b..374c96482a4 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts @@ -597,3 +597,17 @@ export type Patch = object; export type GetGoto = { url: string; }; +export const { + useGetApiResourcesQuery, + useListShortUrlQuery, + useCreateShortUrlMutation, + useDeletecollectionShortUrlMutation, + useGetShortUrlQuery, + useReplaceShortUrlMutation, + useDeleteShortUrlMutation, + useUpdateShortUrlMutation, + useGetShortUrlGotoQuery, + useGetShortUrlStatusQuery, + useReplaceShortUrlStatusMutation, + useUpdateShortUrlStatusMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts b/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts index 82e1339f9bd..18fd3cbee59 100644 --- a/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts +++ b/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts @@ -28,6 +28,7 @@ const createAPIConfig = (app: string, version: string, filterEndpoints?: Endpoin apiFile: `../clients/rtkq/${app}/${version}/baseAPI.ts`, filterEndpoints, tag: true, + hooks: true, ...additional, }, }; @@ -39,6 +40,14 @@ const config: ConfigFile = { exportName: 'generatedAPI', outputFiles: { + // OpenAPI3 client with all endpoints + '../clients/rtkq/legacy/endpoints.gen.ts': { + schemaFile: path.join(basePath, 'public/openapi3.json'), + hooks: true, + tag: true, + apiFile: '../clients/rtkq/legacy/baseAPI.ts', + filterEndpoints: (_name, operation) => !operation.operation.deprecated, + }, '../clients/rtkq/migrate-to-cloud/endpoints.gen.ts': { schemaFile: path.join(basePath, 'public/openapi3.json'), apiFile: '../clients/rtkq/migrate-to-cloud/baseAPI.ts', @@ -77,35 +86,17 @@ const config: ConfigFile = { apiFile: '../clients/rtkq/user/baseAPI.ts', filterEndpoints: ['starDashboardByUid', 'unstarDashboardByUid'], }, - ...createAPIConfig('iam', 'v0alpha1', ['getDisplayMapping']), - ...createAPIConfig('provisioning', 'v0alpha1', filterEndpoints, { hooks: true }), - ...createAPIConfig('folder', 'v1beta1', undefined), - ...createAPIConfig('advisor', 'v0alpha1', [ - 'createCheck', - 'getCheck', - 'listCheck', - 'deleteCheck', - 'updateCheck', - 'listCheckType', - 'updateCheckType', - ]), - ...createAPIConfig('playlist', 'v0alpha1', [ - 'listPlaylist', - 'getPlaylist', - 'createPlaylist', - 'deletePlaylist', - 'replacePlaylist', - ]), - ...createAPIConfig('shorturl', 'v1alpha1'), - ...createAPIConfig('preferences', 'v1alpha1', undefined, { hooks: true }), - ...createAPIConfig('dashboard', 'v0alpha1', ['getSearch']), + ...createAPIConfig('advisor', 'v0alpha1'), ...createAPIConfig('correlations', 'v0alpha1'), + ...createAPIConfig('dashboard', 'v0alpha1'), + ...createAPIConfig('folder', 'v1beta1'), + ...createAPIConfig('iam', 'v0alpha1'), + ...createAPIConfig('playlist', 'v0alpha1'), + ...createAPIConfig('preferences', 'v1alpha1'), + ...createAPIConfig('provisioning', 'v0alpha1'), + ...createAPIConfig('shorturl', 'v1alpha1'), // PLOP_INJECT_API_CLIENT - Used by the API client generator }, }; -function filterEndpoints(name: string) { - return !name.toLowerCase().includes('getapiresources') && !name.toLowerCase().includes('update'); -} - export default config; diff --git a/public/app/api/clients/legacy/index.ts b/public/app/api/clients/legacy/index.ts new file mode 100644 index 00000000000..6a6abcf4054 --- /dev/null +++ b/public/app/api/clients/legacy/index.ts @@ -0,0 +1,6 @@ +import { generatedAPI } from '@grafana/api-clients/rtkq/legacy'; + +export const legacyAPI = generatedAPI.enhanceEndpoints({}); + +// eslint-disable-next-line no-barrel-files/no-barrel-files +export * from '@grafana/api-clients/rtkq/legacy'; diff --git a/public/app/core/reducers/root.ts b/public/app/core/reducers/root.ts index 890879ca7a2..4d26d7bc153 100644 --- a/public/app/core/reducers/root.ts +++ b/public/app/core/reducers/root.ts @@ -3,6 +3,7 @@ import { AnyAction, combineReducers } from 'redux'; import { notificationsAPIv0alpha1, rulesAPIv0alpha1 } from '@grafana/alerting/unstable'; import { allReducers as allApiClientReducers } from '@grafana/api-clients/rtkq'; +import { generatedAPI as legacyAPI } from '@grafana/api-clients/rtkq/legacy'; import sharedReducers from 'app/core/reducers'; import ldapReducers from 'app/features/admin/state/reducers'; import alertingReducers from 'app/features/alerting/state/reducers'; @@ -51,6 +52,7 @@ const rootReducers = { ...templatingReducers, ...supportBundlesReducer, ...authConfigReducers, + [legacyAPI.reducerPath]: legacyAPI.reducer, plugins: pluginsReducer, [alertingApi.reducerPath]: alertingApi.reducer, [notificationsAPIv0alpha1.reducerPath]: notificationsAPIv0alpha1.reducer, diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index 5b2771c879a..34a5500268e 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -4,6 +4,7 @@ import { Middleware } from 'redux'; import { notificationsAPIv0alpha1, rulesAPIv0alpha1 } from '@grafana/alerting/unstable'; import { allMiddleware as allApiClientMiddleware } from '@grafana/api-clients/rtkq'; +import { legacyAPI } from 'app/api/clients/legacy'; import { browseDashboardsAPI } from 'app/features/browse-dashboards/api/browseDashboardsAPI'; import { publicDashboardApi } from 'app/features/dashboard/api/publicDashboardApi'; import { StoreState } from 'app/types/store'; @@ -42,6 +43,7 @@ export function configureStore(initialState?: Partial) { // other Grafana core APIs publicDashboardApi.middleware, browseDashboardsAPI.middleware, + legacyAPI.middleware, ...allApiClientMiddleware, ...extraMiddleware ), From edc7302c2ff51c273decc0de9a02f9e9d561c04e Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Wed, 29 Oct 2025 09:35:53 -0400 Subject: [PATCH 09/72] Docs: Add query variable static options (#113058) --- .../variables/add-template-variables/index.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/sources/visualizations/dashboards/variables/add-template-variables/index.md b/docs/sources/visualizations/dashboards/variables/add-template-variables/index.md index 42cea78cf57..69dbf4a2547 100644 --- a/docs/sources/visualizations/dashboards/variables/add-template-variables/index.md +++ b/docs/sources/visualizations/dashboards/variables/add-template-variables/index.md @@ -161,19 +161,10 @@ Query expressions are different for each data source. For more information, refe 1. [Enter general options](#enter-general-options). 1. Under the **Query options** section of the page, select a target data source in the **Data source** drop-down list. - You can also click **Open advanced data source picker** to see more options, including adding a data source (Admins only). For more information about data sources, refer to [Add a data source](ref:add-a-data-source). -1. In the **Query type** drop-down list, select one of the following options: - - **Label names** - - **Label values** - - **Metrics** - - **Query result** - - **Series query** - - **Classic query** - -1. In the **Query** field, enter a query. +1. In the **Query type** drop-down list, select an option and fill in the query fields accordingly. - The query field varies according to your data source. Some data sources have custom query editors. - Each data source defines how the variable values are extracted. The typical implementation uses every string value returned from the data source response as a variable value. Make sure to double-check the documentation for the data source. - Some data sources let you provide custom "display names" for the values. For instance, the PostgreSQL, MySQL, and Microsoft SQL Server plugins handle this by looking for fields named `__text` and `__value` in the result. Other data sources may look for `text` and `value` or use a different approach. Always remember to double-check the documentation for the data source. @@ -185,9 +176,15 @@ Query expressions are different for each data source. For more information, refe - **On dashboard load** - Queries the data source every time the dashboard loads. This slows down dashboard loading, because the variable query needs to be completed before dashboard can be initialized. - **On time range change** - Queries the data source every time the dashboard loads and when the dashboard time range changes. Use this option if your variable options query contains a time range filter or is dependent on the dashboard time range. +1. (Optional) In the **Static options** section of the page, toggle on the **Use static options** switch to add custom options in addition to the query results: + - Make entries in the **Value** and **Display text** fields. + - Click **+ Add new option** to add another static option. + - Repeat these steps as many times as needed. + 1. (Optional) Configure the settings in the [Selection Options](#configure-variable-selection-options) section: - **Multi-value** - Enables multiple values to be selected at the same time. - - **Include All option** - Enables an option to include all variables. + - **Allow custom values** - Enables users to add custom values to the list. + - **Include All option** - Enables an option to include all variables. Enter a value in the **Custom all value** field to set your own "all" option. 1. In the **Preview of values** section, Grafana displays a list of the current variable values. Review them to ensure they match what you expect. 1. Click **Save dashboard**. From c1808a00c2f3741224a8472789b4e6e139b6366a Mon Sep 17 00:00:00 2001 From: Janos Gub Date: Wed, 29 Oct 2025 15:07:56 +0100 Subject: [PATCH 10/72] Adding adaptive telemetry's own weight (#113167) --- pkg/services/navtree/models.go | 1 + pkg/services/navtree/navtreeimpl/applinks.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index 50ccfcb4cdd..8206a45d5ea 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -21,6 +21,7 @@ const ( WeightAlerting WeightAlertsAndIncidents WeightAIAndML + WeightAdaptiveTelemetry WeightTestingAndSynthetics WeightObservability WeightCloudServiceProviders diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index 7a2f15206dc..e3fea6da25e 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -344,7 +344,7 @@ func (s *ServiceImpl) addPluginToSection(c *contextmodel.ReqContext, treeRoot *n Id: navtree.NavIDAdaptiveTelemetry, SubTitle: "Reduce noise, cut costs, and accelerate troubleshooting by intelligently ingesting only the telemetry data that matters most.", Icon: "adaptive-telemetry", - SortWeight: navtree.WeightAIAndML + 1, // Place under "AI & Machine Learning" + SortWeight: navtree.WeightAdaptiveTelemetry, Children: sectionChildren, Url: "adaptive-telemetry", // Use the icon URL from the first "Adaptive Telemetry" plugin in the list (they will all be the same) From 7dbacddb180c11e518c046886840b3977ff7cc09 Mon Sep 17 00:00:00 2001 From: Matheus Macabu Date: Wed, 29 Oct 2025 15:08:43 +0100 Subject: [PATCH 11/72] CloudMigrations: Check contact point permissions before fetching it (#113159) --- .../cloudmigrationimpl/cloudmigration_test.go | 13 +++++++++++-- .../cloudmigrationimpl/snapshot_mgmt_alerts.go | 10 ++++++++++ .../cloudmigrationimpl/snapshot_mgmt_alerts_test.go | 7 +------ .../cloudmigrationimpl/xorm_store_test.go | 2 +- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go index 449a0ee0817..2d393236801 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go @@ -40,6 +40,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/models" ngalertstore "github.com/grafana/grafana/pkg/services/ngalert/store" ngalertfakes "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/quota/quotatest" @@ -790,7 +791,15 @@ func TestGetPlugins(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - user := &user.SignedInUser{OrgID: 1} + user := &user.SignedInUser{ + OrgID: 1, + Permissions: map[int64]map[string][]string{ + 1: { + pluginaccesscontrol.ActionInstall: {pluginaccesscontrol.ScopeProvider.GetResourceAllScope()}, + pluginaccesscontrol.ActionWrite: {pluginaccesscontrol.ScopeProvider.GetResourceAllScope()}, + }, + }, + } s.pluginStore = pluginstore.NewFakePluginStore([]pluginstore.Plugin{ { @@ -1001,7 +1010,7 @@ func setUpServiceTest(t *testing.T, cfgOverrides ...configOverrides) cloudmigrat mockFolder, &pluginstore.FakePluginStore{}, &pluginsettings.FakePluginSettings{}, - actest.FakeAccessControl{ExpectedEvaluate: true}, + accessControl, fakeAccessControlService, kvstore.ProvideService(sqlStore), &libraryelementsfake.LibraryElementService{}, diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go index a279c09d9db..ae9a1ef52ed 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go @@ -9,9 +9,12 @@ import ( "github.com/prometheus/common/model" "github.com/grafana/grafana/pkg/components/simplejson" + ac "github.com/grafana/grafana/pkg/services/accesscontrol" ngalertapi "github.com/grafana/grafana/pkg/services/ngalert/api/compat" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/provisioning" + "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) @@ -79,6 +82,13 @@ type contactPoint struct { } func (s *Service) getContactPoints(ctx context.Context, signedInUser *user.SignedInUser) ([]contactPoint, error) { + userIsOrgAdmin := signedInUser.HasRole(org.RoleAdmin) + hasAccess, _ := s.accessControl.Evaluate(ctx, signedInUser, ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets, models.ScopeReceiversAll)) + if !userIsOrgAdmin && !hasAccess { + msg := "user '%s' is not allowed to read contact point secrets, missing 'alert.notifications.receivers.secrets:read' permission, which can be granted through the 'Admin' or 'Alerting > Full admin access' roles" + return nil, fmt.Errorf(msg, signedInUser.UserUID) + } + query := provisioning.ContactPointQuery{ OrgID: signedInUser.GetOrgID(), Decrypt: true, // needed to recreate the settings in the target instance. diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go index ec50629c24b..611b10c317b 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "net/http" "testing" "time" @@ -13,7 +12,6 @@ import ( "github.com/prometheus/alertmanager/pkg/labels" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" @@ -127,10 +125,7 @@ func TestGetContactPoints(t *testing.T) { contactPoints, err := s.getContactPoints(ctx, user) require.Nil(t, contactPoints) - - gfErr := errutil.Error{} - require.ErrorAs(t, err, &gfErr) - require.Equal(t, http.StatusForbidden, gfErr.Reason.Status().HTTPStatus()) + require.Contains(t, err.Error(), "alert.notifications.receivers.secrets:read") }) } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go index 2d84b257a5a..6abb63ce54e 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go @@ -822,7 +822,7 @@ func TestEncodeDecode(t *testing.T) { Private: grafanaPrivateKey[:], }, crypto.NewNacl(), - "", + t.TempDir(), ) require.NoError(t, err) From 2472555af0d7e488c424478e08131d4badbdfaa4 Mon Sep 17 00:00:00 2001 From: Brendan O'Handley Date: Wed, 29 Oct 2025 10:15:20 -0400 Subject: [PATCH 12/72] Add to dashboard: expose add to dashboard form component for external apps (#112609) * add extension for drilldown to add to dashboard * reuse configure add to dashboard function callback * structure for drilldown add to dashboard * fix imports * fix tests * expose as a component * remove extension link * get component ready to extend * lazy load component * add component to exposed component registry * update folder structure to not work in explore folder * keep dependencies clean * nice structure to let folks know this is a drilldown integration * update code owners for new file * make exposed component more generic, step one, update component id * step 2, expose add to dashboard form component * add more explicit useAbsolutePath option to form * remove old implementation code for drilldown specific component * commit translation * add comments to avoid breaking changes * add e2e test for add to dashboard form component * fix flaky test * add exposed component id to e2e test app * remove gridPos in buildPanel fallback fn * add code comment for useAbsolutePath's purpose * remove gridPos from e2e test --- .../pages/ExposedComponents.tsx | 17 ++++++++ .../grafana-extensionstest-app/plugin.json | 2 +- .../tests/useExposedComponent.spec.ts | 30 ++++++++++++++ .../src/types/pluginExtensions.ts | 1 + .../addToDashboard/AddToDashboardForm.tsx | 14 ++++++- .../AddToDashboardFormExposedComponent.tsx | 41 +++++++++++++++++++ .../addToDashboard/addToDashboard.ts | 17 +++++++- .../plugins/extensions/registry/setup.ts | 7 ++++ public/locales/en-US/grafana.json | 5 +++ 9 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 public/app/features/dashboard-scene/addToDashboard/AddToDashboardFormExposedComponent.tsx diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/ExposedComponents.tsx b/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/ExposedComponents.tsx index 28775391881..6b284e171bc 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/ExposedComponents.tsx +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/ExposedComponents.tsx @@ -10,6 +10,7 @@ export function ExposedComponents() { const { component: ReusableComponent } = usePluginComponent( 'grafana-extensionexample1-app/reusable-component/v1' ); + const { component: AddToDashboardForm } = usePluginComponent('grafana/add-to-dashboard-form/v1'); if (!ReusableComponent) { return null; @@ -20,6 +21,22 @@ export function ExposedComponents() {
+ {AddToDashboardForm && ( +
+

Save to dashboard (exposed form)

+ ({ + type: 'timeseries', + title: 'E2E Add to Dashboard Panel', + targets: [], + })} + // Ensure navigation works correctly from plugin page + options={{ useAbsolutePath: true }} + onClose={() => {}} + /> +
+ )} ); } diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json b/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json index 90978c2c8a2..57fa87dcde5 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json @@ -80,7 +80,7 @@ "grafanaDependency": ">=10.4.0", "plugins": [], "extensions": { - "exposedComponents": ["grafana-extensionexample1-app/reusable-component/v1"] + "exposedComponents": ["grafana-extensionexample1-app/reusable-component/v1", "grafana/add-to-dashboard-form/v1"] } } } diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/useExposedComponent.spec.ts b/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/useExposedComponent.spec.ts index 9f3d3f0bf35..501c929b612 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/useExposedComponent.spec.ts +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/useExposedComponent.spec.ts @@ -1,6 +1,7 @@ import { test, expect } from '@grafana/plugin-e2e'; import { testIds } from '../testIds'; import pluginJson from '../plugin.json'; +import { ensureExtensionRegistryIsPopulated } from './utils'; test.describe( 'grafana-extensionstest-app', @@ -12,5 +13,34 @@ test.describe( await page.goto(`/a/${pluginJson.id}/exposed-components`); await expect(page.getByTestId(testIds.appB.exposedComponent)).toHaveText('Hello World!'); }); + + test('exposed add-to-dashboard form saves to a new dashboard', async ({ page }) => { + await page.goto(`/a/${pluginJson.id}/exposed-components`); + await ensureExtensionRegistryIsPopulated(page); + + // Wait for the exposed form section to be ready + await expect(page.getByRole('heading', { name: 'Save to dashboard (exposed form)' })).toBeVisible(); + + // Wait for any of the form buttons to render (lazy load) before clicking + const openInNewTab = page.getByRole('button', { name: 'Open in new tab' }); + const cancelBtn = page.getByRole('button', { name: 'Cancel' }); + await Promise.race([expect(openInNewTab).toBeVisible(), expect(cancelBtn).toBeVisible()]); + + // Now wait for the submit button to be visible, then click (role or text) + const openDashboardByRole = page.getByRole('button', { name: 'Open dashboard' }); + const openDashboardByText = page.getByText('Open dashboard'); + if (await openDashboardByRole.isVisible().catch(() => false)) { + await openDashboardByRole.click(); + } else { + await expect(openDashboardByText.first()).toBeVisible(); + await openDashboardByText.first().click(); + } + + // Navigates to /dashboard/new and prepopulates a panel from local storage + await expect(page).toHaveURL(/\/dashboard\/new/); + + // Panel should be created with our custom title + await expect(page.getByText('E2E Add to Dashboard Panel').first()).toBeVisible(); + }); } ); diff --git a/packages/grafana-data/src/types/pluginExtensions.ts b/packages/grafana-data/src/types/pluginExtensions.ts index 47e130afcce..4b988c49a0a 100644 --- a/packages/grafana-data/src/types/pluginExtensions.ts +++ b/packages/grafana-data/src/types/pluginExtensions.ts @@ -230,6 +230,7 @@ export enum PluginExtensionPointPatterns { // Extension Points available in plugins export enum PluginExtensionExposedComponents { CentralAlertHistorySceneV1 = 'grafana/central-alert-history-scene/v1', + AddToDashboardFormV1 = 'grafana/add-to-dashboard-form/v1', } export type PluginExtensionPanelContext = { diff --git a/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx index 17ecf87eb8f..85f3e47ae03 100644 --- a/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx +++ b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx @@ -41,7 +41,11 @@ export interface Props { children?: React.ReactNode; } -export function AddToDashboardForm({ +/** + * Internal implementation used by the exposed versioned wrapper. + * For stability/versioning guidance, refer to AddToDashboardFormExposedComponent. + */ +export function AddToDashboardForm({ onClose, buildPanel, timeRange, @@ -91,7 +95,7 @@ export function AddToDashboardForm({ queries: panel.targets, }); - const error = addToDashboard({ dashboardUid, panel, openInNewTab, timeRange }); + const error = addToDashboard({ dashboardUid, panel, openInNewTab, timeRange, options }); if (error) { setSubmissionError(error); return; @@ -202,3 +206,9 @@ function assertIsSaveToExistingDashboardError( // explicitly assert its type so that TS can narrow down FormDTO to SaveToExistingDashboard // when we use it in the form. } + +export interface AbsolutePathOptions { + useAbsolutePath: boolean; +} + +export default AddToDashboardForm; diff --git a/public/app/features/dashboard-scene/addToDashboard/AddToDashboardFormExposedComponent.tsx b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardFormExposedComponent.tsx new file mode 100644 index 00000000000..3111700e1ee --- /dev/null +++ b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardFormExposedComponent.tsx @@ -0,0 +1,41 @@ +import { lazy, Suspense } from 'react'; + +import { t } from '@grafana/i18n'; + +import { AbsolutePathOptions, Props } from './AddToDashboardForm'; + +// Lazy load the component +const AddToDashboardFormLazy = lazy(() => import('./AddToDashboardForm')); + +/** + * EXPOSED COMPONENT (stable): grafana/add-to-dashboard-form/v1 + * + * This component is exposed to plugins via the Plugin Extensions system. + * Treat its props and user-visible behavior as a stable contract. Do not make + * breaking changes in-place. If you need to change the API or behavior in a + * breaking way, create a new versioned component (e.g. AddToDashboardFormV2) + * and register it under a new ID: "grafana/add-to-dashboard-form/v2". + * + * Consumers should import it using the exposed component ID and pass only the + * supported props. The default buildPanel creates a time series panel; callers + * can supply a custom builder via "buildPanel". + */ +export const AddToDashboardFormExposedComponent = (props: Partial>) => ( + + {})} + buildPanel={ + props.buildPanel ?? + (() => ({ + type: 'timeseries', + title: t('dashboard-scene.add-to-dashboard-form-exposed.title.new-panel', 'New panel'), + targets: [], + })) + } + timeRange={props.timeRange} + options={props.options} + > + {props.children} + + +); diff --git a/public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts b/public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts index e22feb9a502..dfb375fced0 100644 --- a/public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts +++ b/public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts @@ -26,6 +26,9 @@ interface AddPanelToDashboardOptions { dashboardUid?: string; openInNewTab?: boolean; timeRange?: TimeRange; + options?: { + useAbsolutePath?: boolean; + }; } export function addToDashboard({ @@ -33,6 +36,7 @@ export function addToDashboard({ dashboardUid, openInNewTab, timeRange, + options, }: AddPanelToDashboardOptions): SubmissionError | undefined { let dto: DashboardDTO = { meta: {}, @@ -83,7 +87,18 @@ export function addToDashboard({ return; } - locationService.push(locationUtil.stripBaseFromUrl(dashboardURL)); + let navigateToDashboardUrl = locationUtil.stripBaseFromUrl(dashboardURL); + + // External apps need absolute paths to navigate to dashboards correctly. + // Without the leading '/', paths like "dashboard/new" are treated as relative to the current location. + // For example, from "/a/grafana-metricsdrilldown-app", this would incorrectly navigate to + // "/a/grafana-metricsdrilldown-app/dashboard/new" instead of "/dashboard/new". + if (options?.useAbsolutePath) { + navigateToDashboardUrl = '/' + navigateToDashboardUrl; + } + + locationService.push(navigateToDashboardUrl); + return; } diff --git a/public/app/features/plugins/extensions/registry/setup.ts b/public/app/features/plugins/extensions/registry/setup.ts index c4057e22c4b..bb25c487dc3 100644 --- a/public/app/features/plugins/extensions/registry/setup.ts +++ b/public/app/features/plugins/extensions/registry/setup.ts @@ -1,5 +1,6 @@ import { PluginExtensionExposedComponents } from '@grafana/data'; import CentralAlertHistorySceneExposedComponent from 'app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistorySceneExposedComponent'; +import { AddToDashboardFormExposedComponent } from 'app/features/dashboard-scene/addToDashboard/AddToDashboardFormExposedComponent'; import { getCoreExtensionConfigurations } from '../getCoreExtensionConfigurations'; @@ -36,5 +37,11 @@ exposedComponentsRegistry.register({ description: 'Central alert history scene', component: CentralAlertHistorySceneExposedComponent, }, + { + id: PluginExtensionExposedComponents.AddToDashboardFormV1, + title: 'Add to dashboard form', + description: 'Add to dashboard form', + component: AddToDashboardFormExposedComponent, + }, ], }); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index e8c29e9ffe7..3c0c17c7af2 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -5702,6 +5702,11 @@ "open-in-new-tab": "Open in new tab", "title-error-adding-the-panel": "Error adding the panel" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "New panel" + } + }, "annotation-settings-edit": { "back-to-list": "Back to list", "delete": "Delete", From 19826b5b2616f9aef013893f97086bc88b4ffe69 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Wed, 29 Oct 2025 08:15:43 -0600 Subject: [PATCH 13/72] Dashboard Save: Fix the issue of clicking Save button that wouldn't trigger save (#113134) * fix the issue of clicking Save button that wouldn't trigger save * clean up --- eslint-suppressions.json | 5 -- .../saving/SaveDashboardAsForm.tsx | 61 +++++++++++++------ 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index fdcf848a354..c704beaefc3 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2110,11 +2110,6 @@ "count": 1 } }, - "public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx": { - "no-restricted-syntax": { - "count": 4 - } - }, "public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx": { "no-restricted-syntax": { "count": 1 diff --git a/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx index 8f0a383d80f..896c2fa3474 100644 --- a/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx +++ b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx @@ -1,5 +1,4 @@ -import debounce from 'debounce-promise'; -import { ChangeEvent, useState, useEffect } from 'react'; +import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react'; import { UseFormSetValue, useForm } from 'react-hook-form'; import { selectors } from '@grafana/e2e-selectors'; @@ -43,19 +42,48 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) { }, }); - const { errors, isValid, validatingFields } = formState; + const { errors, isValid } = formState; const formValues = watch(); const { state, onSaveDashboard } = useSaveDashboard(false); const [contentSent, setContentSent] = useState<{ title?: string; folderUid?: string }>({}); + const validationTimeoutRef = useRef(); + // Validate title on form mount to catch invalid default values useEffect(() => { trigger('title'); }, [trigger]); + // Cleanup timeout on unmount + useEffect(() => { + return () => { + clearTimeout(validationTimeoutRef.current); + }; + }, []); + + const handleTitleChange = useCallback( + (e: ChangeEvent) => { + setValue('title', e.target.value, { shouldDirty: true }); + clearTimeout(validationTimeoutRef.current); + validationTimeoutRef.current = setTimeout(() => { + trigger('title'); + }, 400); + }, + [setValue, trigger] + ); + const onSave = async (overwrite: boolean) => { + clearTimeout(validationTimeoutRef.current); + + const isTitleValid = await trigger('title'); + + // This prevents the race between the new input and old validation state + if (!isTitleValid) { + return; + } + const data = getValues(); const result = await onSaveDashboard(dashboard, { @@ -88,16 +116,7 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) { ); const saveButton = (overwrite: boolean) => { - const isTitleValidating = !!validatingFields.title; - - return ( - - ); + return ; }; function renderFooter(error?: Error) { const formValuesMatchContentSent = @@ -128,23 +147,27 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) { return (
onSave(false))}> - } invalid={!!errors.title} error={errors.title?.message}> + } + invalid={!!errors.title} + error={errors.title?.message} + > ) => { - setValue('title', e.target.value, { shouldValidate: true }); - }, 400)} /> } invalid={!!errors.description} error={errors.description?.message} @@ -159,7 +182,7 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) { /> - + { setValue('folder', { uid, title }); @@ -177,7 +200,7 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) { /> {!changeInfo.isNew && ( - + )} From 87794bec12ec660e9805edccbf59944230da16f8 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Wed, 29 Oct 2025 15:21:05 +0100 Subject: [PATCH 14/72] Alerting: Add instances with no label value to an ungrouped group (#113170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add instances with no label value to an ungrouped group in alerting triage – these will be collapsed by default --- .../unified/triage/rows/FolderGroupRow.tsx | 3 +- .../alerting/unified/triage/rows/GroupRow.tsx | 15 +++++++-- .../alerting/unified/triage/rows/utils.ts | 6 ++-- .../unified/triage/scene/Workbench.tsx | 32 +++++++++++++------ .../alerting/unified/triage/scene/utils.ts | 9 ++---- .../features/alerting/unified/triage/types.ts | 5 ++- 6 files changed, 46 insertions(+), 24 deletions(-) diff --git a/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx b/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx index 4809a44573a..1e5e1156452 100644 --- a/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; +import { isString } from 'lodash'; import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; @@ -27,7 +28,7 @@ export const FolderGroupRow = ({ row, leftColumnWidth, rowKey, depth = 0, childr title={ - {row.metadata.value} + {isString(row.metadata.value) && {row.metadata.value}} } isOpenByDefault={true} diff --git a/public/app/features/alerting/unified/triage/rows/GroupRow.tsx b/public/app/features/alerting/unified/triage/rows/GroupRow.tsx index 4499672dd6e..1bb73674165 100644 --- a/public/app/features/alerting/unified/triage/rows/GroupRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/GroupRow.tsx @@ -5,9 +5,10 @@ import { AlertLabel } from '@grafana/alerting/unstable'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import { GenericGroupedRow } from '../types'; +import { EmptyLabelValue, GenericGroupedRow } from '../types'; import { GenericRow } from './GenericRow'; +import { formatLabelValue } from './utils'; interface GroupRowProps { row: GenericGroupedRow; @@ -19,13 +20,21 @@ interface GroupRowProps { export const GroupRow = ({ row, leftColumnWidth, rowKey, depth = 0, children }: GroupRowProps) => { const styles = useStyles2(getStyles); + const isEmptyValue = row.metadata.value === EmptyLabelValue; return ( } - isOpenByDefault={true} + title={ + + } + isOpenByDefault={!isEmptyValue} leftColumnClassName={styles.groupRow} rightColumnClassName={styles.groupRow} depth={depth} diff --git a/public/app/features/alerting/unified/triage/rows/utils.ts b/public/app/features/alerting/unified/triage/rows/utils.ts index 6a4d0731643..d5b193ee755 100644 --- a/public/app/features/alerting/unified/triage/rows/utils.ts +++ b/public/app/features/alerting/unified/triage/rows/utils.ts @@ -1,4 +1,4 @@ -import { WorkbenchRow } from '../types'; +import { EmptyLabelValue, LabelValue, WorkbenchRow } from '../types'; // Generate unique keys for WorkbenchRow items export function generateRowKey(row: WorkbenchRow, fallbackIndex: number): string { @@ -8,6 +8,8 @@ export function generateRowKey(row: WorkbenchRow, fallbackIndex: number): string } else { // For GenericGroupedRow, create key from label and value const groupedRow = row; - return `group-${groupedRow.metadata.label}-${groupedRow.metadata.value}`; + return `group-${groupedRow.metadata.label}-${formatLabelValue(groupedRow.metadata.value)}`; } } + +export const formatLabelValue = (value: LabelValue): string => (value === EmptyLabelValue ? '' : value); diff --git a/public/app/features/alerting/unified/triage/scene/Workbench.tsx b/public/app/features/alerting/unified/triage/scene/Workbench.tsx index 356a2924461..f202a5cd0a0 100644 --- a/public/app/features/alerting/unified/triage/scene/Workbench.tsx +++ b/public/app/features/alerting/unified/triage/scene/Workbench.tsx @@ -1,3 +1,4 @@ +import { isEmpty } from 'lodash'; import { ArrayValues } from 'type-fest'; import { DataFrame, PanelData } from '@grafana/data'; @@ -6,7 +7,7 @@ import { useQueryRunner, useTimeRange, useVariableValues } from '@grafana/scenes import { Workbench } from '../Workbench'; import { DEFAULT_FIELDS, METRIC_NAME, VARIABLES } from '../constants'; -import { AlertRuleRow, GenericGroupedRow, WorkbenchRow } from '../types'; +import { AlertRuleRow, EmptyLabelValue, GenericGroupedRow, WorkbenchRow } from '../types'; import { convertTimeRangeToDomain, getDataQuery, useQueryFilter } from './utils'; @@ -79,29 +80,40 @@ function groupData(dataPoints: DataPoint[], groupBy: string[], depth: number): W } const groupByKey = groupBy[depth]; - const grouped = new Map(); + const grouped = new Map(); for (const dp of dataPoints) { - const key = String(dp[groupByKey] ?? 'undefined'); - if (!grouped.has(key)) { - grouped.set(key, []); + const mapKey = dp[groupByKey] ?? EmptyLabelValue; + if (!grouped.has(mapKey)) { + grouped.set(mapKey, []); } - grouped.get(key)?.push(dp); + grouped.get(mapKey)?.push(dp); } const result: GenericGroupedRow[] = []; + const emptyGroups: GenericGroupedRow[] = []; + for (const [value, rows] of grouped.entries()) { - result.push({ + const labelValue = isEmpty(value) ? EmptyLabelValue : value; + + const group: GenericGroupedRow = { type: 'group', metadata: { label: groupByKey, - value: value, + value: labelValue, }, rows: groupData(rows, groupBy, depth + 1), - }); + }; + + // Separate empty label groups to append at the end + if (group.metadata.value === EmptyLabelValue) { + emptyGroups.push(group); + } else { + result.push(group); + } } - return result; + return [...result, ...emptyGroups]; } // @TODO narrower types for PanelData! (if possible) diff --git a/public/app/features/alerting/unified/triage/scene/utils.ts b/public/app/features/alerting/unified/triage/scene/utils.ts index 19886a760e0..456e3f3c7bf 100644 --- a/public/app/features/alerting/unified/triage/scene/utils.ts +++ b/public/app/features/alerting/unified/triage/scene/utils.ts @@ -1,6 +1,6 @@ import { TimeRange } from '@grafana/data'; import { SceneDataQuery } from '@grafana/scenes'; -import { useVariableValue, useVariableValues } from '@grafana/scenes-react'; +import { useVariableValue } from '@grafana/scenes-react'; import { DataSourceRef } from '@grafana/schema'; import { DATASOURCE_UID, VARIABLES } from '../constants'; @@ -44,11 +44,6 @@ export function convertTimeRangeToDomain(timeRange: TimeRange): Domain { * This hook will create a Prometheus label matcher string from the "groupBy" and "filters" variables */ export function useQueryFilter(): string { - const [groupBy = []] = useVariableValues(VARIABLES.groupBy); const [filters = ''] = useVariableValue(VARIABLES.filters); - - const groupByFilter = stringifyGroupFilter(groupBy); - const queryFilter = [groupByFilter, filters].filter((s) => Boolean(s)).join(','); - - return queryFilter; + return filters; } diff --git a/public/app/features/alerting/unified/triage/types.ts b/public/app/features/alerting/unified/triage/types.ts index b599f46cb33..c878f8f303c 100644 --- a/public/app/features/alerting/unified/triage/types.ts +++ b/public/app/features/alerting/unified/triage/types.ts @@ -18,7 +18,10 @@ export interface GenericGroupedRow { type: 'group'; metadata: { label: string; - value: string; + value: LabelValue; }; rows: WorkbenchRow[]; } + +export type LabelValue = string | typeof EmptyLabelValue; +export const EmptyLabelValue = Symbol('empty label value'); From 04ab5529509e438b5fa52a611ea1abf5d84548b9 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:33:48 +0100 Subject: [PATCH 15/72] DashboardControls: Render UNSAFE hidden dashboard controls (#113046) * Render UNSAFE hidden dashboard controls * Remove unused imports * Extract to function and write test * Remove unnecessary context from test * Remove exclamation --- .../scene/DashboardControls.test.tsx | 43 ++++++++++++++++++- .../scene/DashboardControls.tsx | 18 +++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx index c01808f411e..c13e65574be 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx @@ -1,7 +1,7 @@ import { render } from '@testing-library/react'; import { selectors } from '@grafana/e2e-selectors'; -import { SceneVariableSet, TextBoxVariable } from '@grafana/scenes'; +import { SceneVariableSet, ScopesVariable, TextBoxVariable } from '@grafana/scenes'; import { DashboardControls, DashboardControlsState } from './DashboardControls'; import { DashboardScene } from './DashboardScene'; @@ -97,6 +97,47 @@ describe('DashboardControls', () => { expect(renderer.queryByTestId(selectors.pages.Dashboard.Controls)).not.toBeInTheDocument(); }); + + it('should render ScopesVariable Component even when hidden', () => { + const scopeVariable = new ScopesVariable({ + enable: true, + }); + + const dashboard = new DashboardScene({ + uid: 'test-dashboard', + $variables: new SceneVariableSet({ + variables: [scopeVariable], + }), + controls: new DashboardControls({ + hideTimeControls: true, + hideVariableControls: true, + hideLinksControls: true, + hideDashboardControls: true, + }), + }); + + dashboard.activate(); + + const controls = dashboard.state.controls as DashboardControls; + + // Mock the Component getter - use 'as any' to bypass TypeScript's getter checking + // Return a component function (not JSX directly) that renders our test element + // eslint-disable-next-line @typescript-eslint/no-explicit-any + jest.spyOn(scopeVariable as any, 'Component', 'get').mockReturnValue(() => { + return
Mocked Component
; + }); + + const renderer = render(); + + // Verify UNSAFE_renderAsHidden is set (required for renderHiddenVariables to include it) + expect(scopeVariable.UNSAFE_renderAsHidden).toBe(true); + + // Check that the mocked component is rendered - this proves renderHiddenVariables + // accessed the Component getter and rendered it + expect(renderer.getByText('Mocked Component')).toBeInTheDocument(); + + jest.restoreAllMocks(); + }); }); describe('UrlSync', () => { diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx index e6a8e7ab8a0..921874732eb 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx @@ -153,7 +153,8 @@ function DashboardControlsRenderer({ model }: SceneComponentProps; + + return {renderHiddenVariables(dashboard)}; } return ( @@ -200,6 +201,21 @@ function DataLayerControls({ dashboard }: { dashboard: DashboardScene }) { ); } +function renderHiddenVariables(dashboard: DashboardScene) { + const { variables } = sceneGraph.getVariables(dashboard).useState(); + const renderAsHiddenVariables = variables.filter((v) => v.UNSAFE_renderAsHidden); + if (renderAsHiddenVariables && renderAsHiddenVariables.length > 0) { + return ( + <> + {renderAsHiddenVariables.map((v) => ( + + ))} + + ); + } + return null; +} + function getStyles(theme: GrafanaTheme2) { return { controls: css({ From 284648df9e5d40a4c9d2d6066835c77d6779d108 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Wed, 29 Oct 2025 11:06:56 -0400 Subject: [PATCH 16/72] SQL Expressions: Point to grafana GMS fork (#113104) Use fork that does not have cgo as default, had to revert build tag method attempt since it broke things like running go test on macs (without the tag) #112289. --- apps/advisor/go.mod | 2 +- apps/advisor/go.sum | 4 ++-- apps/iam/go.mod | 2 +- apps/iam/go.sum | 4 ++-- go.mod | 8 +++++--- go.sum | 12 ++++-------- go.work.sum | 6 ++++-- pkg/expr/sql/db_test.go | 12 ------------ 8 files changed, 19 insertions(+), 31 deletions(-) diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index da70ceca234..1bfc3c9671a 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -96,7 +96,7 @@ require ( github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad // indirect github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e // indirect github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect - github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 // indirect + github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/fatih/color v1.18.0 // indirect diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index 89d9baaa4b4..8594bd52e94 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -383,8 +383,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/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/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 h1:ScHTwNbcVC6JH1OSyXzj8S4w67BIpRXwTSjrac3/PSw= +github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33/go.mod h1:8pvvk5OLaLN9LLxghyczUapn/97l+mBgIb10qC1LG84= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 154a6bb0632..b98eea39b77 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -165,7 +165,7 @@ require ( github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad // indirect github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e // indirect github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect - github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 // indirect + github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index ad77e3a1a30..8a7ec4477df 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -499,8 +499,8 @@ github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTE 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/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 h1:ScHTwNbcVC6JH1OSyXzj8S4w67BIpRXwTSjrac3/PSw= +github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33/go.mod h1:8pvvk5OLaLN9LLxghyczUapn/97l+mBgIb10qC1LG84= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= diff --git a/go.mod b/go.mod index 6c3cf08f214..3b1a8ff4dd5 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/dgraph-io/badger/v4 v4.7.0 // @grafana/grafana-search-and-storage github.com/dlmiddlecote/sqlstats v1.0.2 // @grafana/grafana-backend-group github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e // @grafana/grafana-datasources-core-services - github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 // @grafana/grafana-datasources-core-services + github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 // @grafana/grafana-datasources-core-services github.com/dustin/go-humanize v1.0.1 // @grafana/observability-traces-and-profiling github.com/emicklei/go-restful/v3 v3.13.0 // @grafana/grafana-app-platform-squad github.com/fatih/color v1.18.0 // @grafana/grafana-backend-group @@ -404,7 +404,6 @@ require ( github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect - github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad // indirect github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect github.com/dolthub/maphash v0.1.0 // indirect github.com/edsrzf/mmap-go v1.2.0 // indirect @@ -583,7 +582,6 @@ require ( github.com/stoewer/go-strcase v1.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tetratelabs/wazero v1.8.2 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect @@ -678,6 +676,10 @@ exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible // lock for mysql tsdb compat replace github.com/go-sql-driver/mysql => github.com/go-sql-driver/mysql v1.7.1 +// Use our fork of dolthub/go-mysql-server which makes non-cgo the default +// since using a build tag is not sufficient for some use cases (e.g. developers tests in IDE). +replace github.com/dolthub/go-mysql-server => github.com/grafana/go-mysql-server v0.20.1-grafana1 + // v1.* versions were retracted, we need to stick with v0.*. This should work // without the exclude, but this otherwise gets pulled in as a transitive // dependency. diff --git a/go.sum b/go.sum index 799c4a97b4a..a6d4d890765 100644 --- a/go.sum +++ b/go.sum @@ -1120,16 +1120,12 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= -github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad h1:66ZPawHszNu37VPQckdhX1BPPVzREsGgNxQeefnlm3g= -github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= -github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e h1:7pAttAqWaudUAsM9iHASi/4eFBK+qn4qeaNto7g8bK4= -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/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 h1:ScHTwNbcVC6JH1OSyXzj8S4w67BIpRXwTSjrac3/PSw= +github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33/go.mod h1:8pvvk5OLaLN9LLxghyczUapn/97l+mBgIb10qC1LG84= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= @@ -1617,6 +1613,8 @@ github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz2 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/go-mysql-server v0.20.1-grafana1 h1:yA4Mzt+tTdIlQutBUaiPnepULPQ7CS4hMu2GOpHqT6s= +github.com/grafana/go-mysql-server v0.20.1-grafana1/go.mod h1:EeYR0apo+8j2Dyxmn2ghkPlirO2S5mT1xHBrA+Efys8= 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-20250318131618-74242eea118d h1:oXRJlb9UjVsl6LhqBdbyAQ9YFhExwsj4bjh5vwMNRZY= @@ -2463,8 +2461,6 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= -github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/thanos-io/objstore v0.0.0-20240818203309-0363dadfdfb1 h1:z0v9BB/p7s4J6R//+0a5M3wCld8KzNjrGRLIwXfrAZk= github.com/thanos-io/objstore v0.0.0-20240818203309-0363dadfdfb1/go.mod h1:3ukSkG4rIRUGkKM4oIz+BSuUx2e3RlQVVv3Cc3W+Tv4= github.com/thejerf/slogassert v0.3.4 h1:VoTsXixRbXMrRSSxDjYTiEDCM4VWbsYPW5rB/hX24kM= diff --git a/go.work.sum b/go.work.sum index d78d14d4bb1..8406037217a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -576,8 +576,7 @@ github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 h1:7/v8q9X github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81/go.mod h1:siLfyv2c92W1eN/R4QqG/+RjjX5W2+gCTRjZxBjI3TY= github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw= github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0= -github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 h1:ScHTwNbcVC6JH1OSyXzj8S4w67BIpRXwTSjrac3/PSw= -github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33/go.mod h1:8pvvk5OLaLN9LLxghyczUapn/97l+mBgIb10qC1LG84= +github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 h1:q1oJaUPdmpDm/VyXosjgPgr6wS7c5iV2p0PwJD73bUI= @@ -750,6 +749,9 @@ github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6L github.com/grafana/dskit v0.0.0-20250818234656-8ff9c6532e85/go.mod h1:kImsvJ1xnmeT9Z6StK+RdEKLzlpzBsKwJbEQfmBJdFs= 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/go-mysql-server v0.20.1-0.20251027172658-317a8d46ffa4/go.mod h1:EeYR0apo+8j2Dyxmn2ghkPlirO2S5mT1xHBrA+Efys8= +github.com/grafana/go-mysql-server v0.20.1-grafana1 h1:yA4Mzt+tTdIlQutBUaiPnepULPQ7CS4hMu2GOpHqT6s= +github.com/grafana/go-mysql-server v0.20.1-grafana1/go.mod h1:EeYR0apo+8j2Dyxmn2ghkPlirO2S5mT1xHBrA+Efys8= github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana-app-sdk/logging v0.48.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= diff --git a/pkg/expr/sql/db_test.go b/pkg/expr/sql/db_test.go index ff5ecac022f..c67c23b32b9 100644 --- a/pkg/expr/sql/db_test.go +++ b/pkg/expr/sql/db_test.go @@ -278,18 +278,6 @@ func TestNaNBecomesNull(t *testing.T) { require.NoError(t, err) } -func TestErrorsFromGoMySQLServerAreFlagged(t *testing.T) { - const GmsNotImplemented = "TRUNCATE" // not implemented in go-mysql-server as of 2025-04-11 - - db := DB{} - - query := `SELECT ` + GmsNotImplemented + `(123.456, 2);` - - _, err := db.QueryFrames(context.Background(), &testTracer{}, "sqlExpressionRefId", query, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "error from the sql expression engine") -} - func TestFrameToSQLAndBack_JSONRoundtrip(t *testing.T) { expectedFrame := &data.Frame{ RefID: "json_test", From 51b39d8c6ec715d9b4eaa9bf2757b88a2c0368df Mon Sep 17 00:00:00 2001 From: Sergej-Vlasov <37613182+Sergej-Vlasov@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:07:55 +0000 Subject: [PATCH 17/72] DashboardScene: Ignore defaults changes when exiting edit mode (#112796) * exit dashboard without confirmation with only optional changes * centralise * clean up logic * export to util --- .../features/dashboard-scene/saving/DashboardPrompt.tsx | 4 ++-- .../app/features/dashboard-scene/scene/DashboardScene.tsx | 3 ++- public/app/features/dashboard-scene/utils/utils.ts | 8 ++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx b/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx index 134e2cedbab..fb44176788f 100644 --- a/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx +++ b/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx @@ -16,7 +16,7 @@ import { DashboardMeta } from 'app/types/dashboard'; import { SaveLibraryVizPanelModal } from '../panel-edit/SaveLibraryVizPanelModal'; import { DashboardScene } from '../scene/DashboardScene'; -import { getLibraryPanelBehavior, isLibraryPanel } from '../utils/utils'; +import { getLibraryPanelBehavior, hasActualSaveChanges, isLibraryPanel } from '../utils/utils'; interface DashboardPromptProps { dashboard: DashboardScene; @@ -200,7 +200,7 @@ export function ignoreChanges(scene: DashboardScene | null) { return true; } - return !canSave || fromScript || fromFile; + return !canSave || fromScript || fromFile || (scene.state.isEditing && !hasActualSaveChanges(scene)); } export function isEmptyDashboard( diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 0cabdf459a2..748d3e7986e 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -72,6 +72,7 @@ import { getDefaultVizPanel, getLayoutManagerFor, getPanelIdForVizPanel, + hasActualSaveChanges, } from '../utils/utils'; import { SchemaV2EditorDrawer } from '../v2schema/SchemaV2EditorDrawer'; @@ -316,7 +317,7 @@ export class DashboardScene extends SceneObjectBase impleme return; } - if (!this.state.isDirty || skipConfirm || this.managedResourceCannotBeEdited()) { + if (!this.state.isDirty || skipConfirm || !hasActualSaveChanges(this) || this.managedResourceCannotBeEdited()) { this.exitEditModeConfirmed(restoreInitialState || this.state.isDirty); return; } diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index 12dba08eda4..419ce760b21 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -427,3 +427,11 @@ export function hasLibraryPanelsInV1Dashboard(dashboard: Dashboard | undefined): } export const dashboardLog = createLogger('Dashboard'); + +/** + * Checks if there are save changes but not counting time range, refresh rate and default variable value change + */ +export function hasActualSaveChanges(dashboard: DashboardScene) { + const changes = dashboard.getDashboardChanges(); + return !!changes.diffCount; +} From 7eb8a9af99c74294525c84f2188e1610c81f2d49 Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:08:08 +0100 Subject: [PATCH 18/72] docs(alerting): clarify notification group deletion after group interval elapses (#113160) --- .../fundamentals/notifications/group-alert-notifications.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md b/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md index 64b1e83ef54..91b5f217315 100644 --- a/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md +++ b/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md @@ -159,7 +159,9 @@ Once the first notification has been sent for a new group of alerts, the group i When the group interval timer elapses, the system resets the group interval timer and sends a notification only if there were group changes. This process repeats until there are no more alerts. -It's important to note that an alert instance exits the group after being resolved and notified of its state change. When no alerts remain, the group is deleted, and then the group wait timer handles the first notification for the next incoming alert once again. +It's important to note that an alert instance exits the group after being resolved and notified of its state change. + +When the group interval timer elapses and no alerts remain, the group is deleted. The [group wait timer](#group-wait) will then start again the next time a new alert arrives. ### Repeat interval From 3bfbbb1961e761ef3a2cdc293d07f6eb101fa742 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:33:43 -0400 Subject: [PATCH 19/72] Plugins: Skip angular check for CDN source (#113163) --- .../angularinspector/angularinspector.go | 5 +++ .../angularinspector/angularinspector_test.go | 32 +++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/pkg/plugins/manager/loader/angular/angularinspector/angularinspector.go b/pkg/plugins/manager/loader/angular/angularinspector/angularinspector.go index e67b04b69f2..98840189389 100644 --- a/pkg/plugins/manager/loader/angular/angularinspector/angularinspector.go +++ b/pkg/plugins/manager/loader/angular/angularinspector/angularinspector.go @@ -35,6 +35,11 @@ func NewPatternListInspector(detectorsProvider angulardetector.DetectorsProvider } func (i *PatternsListInspector) Inspect(ctx context.Context, p *plugins.Plugin) (isAngular bool, err error) { + // CDN plugins are ignored because they should not be using Angular + if p.Class == plugins.ClassCDN { + return false, nil + } + f, err := p.FS.Open("module.js") if err != nil { if errors.Is(err, plugins.ErrFileNotExist) { diff --git a/pkg/plugins/manager/loader/angular/angularinspector/angularinspector_test.go b/pkg/plugins/manager/loader/angular/angularinspector/angularinspector_test.go index f45ae982d09..47c5af990b9 100644 --- a/pkg/plugins/manager/loader/angular/angularinspector/angularinspector_test.go +++ b/pkg/plugins/manager/loader/angular/angularinspector/angularinspector_test.go @@ -27,17 +27,17 @@ func (d *fakeDetector) String() string { } func TestPatternsListInspector(t *testing.T) { - plugin := &plugins.Plugin{ - FS: plugins.NewInMemoryFS(map[string][]byte{"module.js": nil}), - } - for _, tc := range []struct { name string + plugin *plugins.Plugin fakeDetectors []*fakeDetector exp func(t *testing.T, r bool, err error, fakeDetectors []*fakeDetector) }{ { name: "calls the detectors in sequence until true is returned", + plugin: &plugins.Plugin{ + FS: plugins.NewInMemoryFS(map[string][]byte{"module.js": nil}), + }, fakeDetectors: []*fakeDetector{ {returns: false}, {returns: true}, @@ -53,6 +53,9 @@ func TestPatternsListInspector(t *testing.T) { }, { name: "calls the detectors in sequence and returns false as default", + plugin: &plugins.Plugin{ + FS: plugins.NewInMemoryFS(map[string][]byte{"module.js": nil}), + }, fakeDetectors: []*fakeDetector{ {returns: false}, {returns: false}, @@ -65,13 +68,30 @@ func TestPatternsListInspector(t *testing.T) { }, }, { - name: "empty detectors should return false", + name: "empty detectors should return false", + plugin: &plugins.Plugin{ + FS: plugins.NewInMemoryFS(map[string][]byte{"module.js": nil}), + }, fakeDetectors: nil, exp: func(t *testing.T, r bool, err error, fakeDetectors []*fakeDetector) { require.NoError(t, err) require.False(t, r, "inspector should return false") }, }, + { + name: "CDN plugins return false without calling detectors", + plugin: &plugins.Plugin{ + Class: plugins.ClassCDN, + }, + fakeDetectors: []*fakeDetector{ + {returns: true}, + }, + exp: func(t *testing.T, r bool, err error, fakeDetectors []*fakeDetector) { + require.NoError(t, err) + require.False(t, r, "inspector should return false for CDN plugins") + require.Equal(t, 0, fakeDetectors[0].calls, "detectors should not be called for CDN plugins") + }, + }, } { t.Run(tc.name, func(t *testing.T) { detectors := make([]angulardetector.AngularDetector, 0, len(tc.fakeDetectors)) @@ -79,7 +99,7 @@ func TestPatternsListInspector(t *testing.T) { detectors = append(detectors, angulardetector.AngularDetector(d)) } inspector := NewPatternListInspector(&angulardetector.StaticDetectorsProvider{Detectors: detectors}) - r, err := inspector.Inspect(context.Background(), plugin) + r, err := inspector.Inspect(context.Background(), tc.plugin) tc.exp(t, r, err, tc.fakeDetectors) }) } From 269c145051eed3f235896b653e62e440b866da1b Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Wed, 29 Oct 2025 12:14:12 -0400 Subject: [PATCH 20/72] SaveProvisionedDashboardForm: Bug fix repo type incorrectly showing in preview banner (#113120) * SaveProvisionedDashboardForm: fix repo type incorrectly showing in preview banner --- .../Dashboards/SaveProvisionedDashboardForm.tsx | 9 +++++---- .../components/Folders/NewProvisionedFolderForm.tsx | 2 +- .../hooks/useProvisionedRequestHandler.test.ts | 7 +------ .../provisioning/hooks/useProvisionedRequestHandler.ts | 4 ++-- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx index 6af33a5cbea..bffaab79a73 100644 --- a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx +++ b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx @@ -86,7 +86,7 @@ export function SaveProvisionedDashboardForm({ ); const navigateToPreview = useCallback( - (ref: string, path: string, repoType: string) => { + (ref: string, path: string, repoType?: string) => { const url = buildResourceBranchRedirectUrl({ baseUrl: `${PROVISIONING_URL}/${defaultValues.repo}/dashboard/preview/${path}`, paramName: 'ref', @@ -110,7 +110,7 @@ export function SaveProvisionedDashboardForm({ }, [dashboard, defaultValues.folder?.uid, drawer, panelEditor, request?.data?.resource]); const onWriteSuccess = useCallback( - ({ repoType }: ProvisionedOperationInfo, upsert: Resource) => { + (upsert: Resource) => { handleDismiss(); if (isNew && upsert?.metadata.name) { handleNewDashboard(upsert); @@ -118,7 +118,7 @@ export function SaveProvisionedDashboardForm({ // if pushed to an existing but non-configured branch, navigate to preview page if (ref !== repository?.branch && ref) { - navigateToPreview(ref, path, repoType); + navigateToPreview(ref, path, repository?.type); return; } @@ -127,7 +127,7 @@ export function SaveProvisionedDashboardForm({ editPanel: null, }); }, - [isNew, path, ref, repository?.branch, handleDismiss, handleNewDashboard, navigateToPreview] + [isNew, path, ref, repository?.branch, repository?.type, handleDismiss, handleNewDashboard, navigateToPreview] ); const onBranchSuccess = useCallback( @@ -147,6 +147,7 @@ export function SaveProvisionedDashboardForm({ request, workflow, resourceType: 'dashboard', + repository, handlers: { onBranchSuccess: ({ ref, path }, info, resource) => onBranchSuccess(ref, path, info, resource), onWriteSuccess, diff --git a/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx b/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx index 3b9d62916a4..109277d38a7 100644 --- a/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx +++ b/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx @@ -92,7 +92,7 @@ function FormContent({ initialValues, repository, workflowOptions, folder, onDis handlers: { onDismiss, onBranchSuccess, - onWriteSuccess: (_, resource) => onWriteSuccess(resource), + onWriteSuccess, onError, }, }); diff --git a/public/app/features/provisioning/hooks/useProvisionedRequestHandler.test.ts b/public/app/features/provisioning/hooks/useProvisionedRequestHandler.test.ts index 0a0e48cf498..7e06b38eff1 100644 --- a/public/app/features/provisioning/hooks/useProvisionedRequestHandler.test.ts +++ b/public/app/features/provisioning/hooks/useProvisionedRequestHandler.test.ts @@ -153,12 +153,7 @@ describe('useProvisionedRequestHandler', () => { ); expect(handlers.onWriteSuccess).toHaveBeenCalledWith( - expect.objectContaining({ - resourceType: 'dashboard', - repoType: 'git', - workflow: 'write', - }), - expect.any(Object) + expect.objectContaining({ kind: 'Dashboard', metadata: expect.objectContaining({ name: 'test-dashboard' }) }) ); expect(handlers.onDismiss).toHaveBeenCalled(); }); diff --git a/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts b/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts index 7fc3bb052fe..188f6218950 100644 --- a/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts +++ b/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts @@ -29,7 +29,7 @@ interface RequestHandlers { info: ProvisionedOperationInfo, resource: Resource ) => void; - onWriteSuccess?: (info: ProvisionedOperationInfo, resource: Resource) => void; + onWriteSuccess?: (resource: Resource) => void; onError?: (error: unknown, info: ProvisionedOperationInfo) => void; onDismiss?: () => void; } @@ -116,7 +116,7 @@ export function useProvisionedRequestHandler({ // refetch folder items after success if folderUID is passed in dispatch(refetchChildren({ parentUID: folderUID || repository?.name, pageSize: PAGE_SIZE })); } - handlers.onWriteSuccess(info, resourceData); + handlers.onWriteSuccess(resourceData); } handlers.onDismiss?.(); From ec6e516e28a8bf4a25b9e1d835e369e06075c2c0 Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Wed, 29 Oct 2025 12:14:57 -0400 Subject: [PATCH 21/72] DashboardEmpty: Disable import dashboard function when a new dashboard is git provisioned (#113122) DashboardEmpty: Disable import dashboard function when a new dashboar is git provisioned --- .../dashboard/dashgrid/DashboardEmpty/DashboardEmptyHooks.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmptyHooks.ts b/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmptyHooks.ts index 7340226a7a2..d0432f5e606 100644 --- a/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmptyHooks.ts +++ b/public/app/features/dashboard/dashgrid/DashboardEmpty/DashboardEmptyHooks.ts @@ -73,8 +73,9 @@ export const useOnAddLibraryPanel = ({ dashboard, canCreate, isReadOnlyRepo }: H }; export const useOnImportDashboard = ({ dashboard, canCreate, isReadOnlyRepo }: HookProps) => { + const isProvisioned = dashboard instanceof DashboardScene && dashboard.isManagedRepository(); return useMemo(() => { - if (!canCreate || isReadOnlyRepo) { + if (!canCreate || isProvisioned || isReadOnlyRepo) { return undefined; } @@ -82,5 +83,5 @@ export const useOnImportDashboard = ({ dashboard, canCreate, isReadOnlyRepo }: H DashboardInteractions.emptyDashboardButtonClicked({ item: 'import_dashboard' }); onImportDashboardImpl(); }; - }, [canCreate, isReadOnlyRepo]); + }, [canCreate, isReadOnlyRepo, isProvisioned]); }; From af8d166b909c941884c3c28188ed8d780596f006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Wed, 29 Oct 2025 17:17:07 +0100 Subject: [PATCH 22/72] Chore: Update `node` to v24 (#112649) --- .nvmrc | 2 +- Dockerfile | 2 +- contribute/developer-guide.md | 2 +- .../grafana-extensionstest-app/package.json | 2 +- .../grafana-test-datasource/package.json | 2 +- package.json | 2 +- packages/grafana-data/package.json | 2 +- .../src/vector/FunctionalVector.ts | 6 +- packages/grafana-e2e-selectors/package.json | 2 +- packages/grafana-flamegraph/package.json | 2 +- .../grafana-o11y-ds-frontend/package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-sql/package.json | 2 +- packages/grafana-test-utils/package.json | 2 +- packages/grafana-ui/package.json | 2 +- .../datasource/azuremonitor/package.json | 2 +- .../datasource/cloud-monitoring/package.json | 2 +- .../package.json | 2 +- .../grafana-pyroscope-datasource/package.json | 2 +- .../grafana-testdata-datasource/package.json | 2 +- .../plugins/datasource/graphite/package.json | 2 +- .../plugins/datasource/jaeger/package.json | 2 +- .../app/plugins/datasource/loki/package.json | 2 +- .../app/plugins/datasource/mssql/package.json | 2 +- .../app/plugins/datasource/mysql/package.json | 2 +- .../app/plugins/datasource/parca/package.json | 2 +- .../app/plugins/datasource/tempo/package.json | 2 +- .../plugins/datasource/zipkin/package.json | 2 +- yarn.lock | 124 +++++++++++------- 29 files changed, 106 insertions(+), 78 deletions(-) diff --git a/.nvmrc b/.nvmrc index aebd91c521b..c519bf5baaa 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v22.16.0 +v24.11.0 diff --git a/Dockerfile b/Dockerfile index 0edb989ac76..909281146e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ ARG JS_SRC=js-builder 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 --platform=${JS_PLATFORM} node:24-alpine AS js-builder-base # Javascript build stage FROM --platform=${JS_PLATFORM} ${JS_IMAGE} AS js-builder ARG JS_NODE_ENV=production diff --git a/contribute/developer-guide.md b/contribute/developer-guide.md index eaa8334926c..65dadfae463 100644 --- a/contribute/developer-guide.md +++ b/contribute/developer-guide.md @@ -18,7 +18,7 @@ We recommend using [Homebrew](https://brew.sh/) for installing any missing depen ``` brew install git brew install go -brew install node@22 +brew install node@24 ``` ### Windows diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json b/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json index e1960c9515e..12829268e71 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json @@ -13,7 +13,7 @@ "devDependencies": { "@grafana/plugin-configs": "workspace:*", "@types/lodash": "4.17.7", - "@types/node": "22.15.0", + "@types/node": "24.9.2", "@types/prismjs": "1.26.4", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/e2e-playwright/test-plugins/grafana-test-datasource/package.json b/e2e-playwright/test-plugins/grafana-test-datasource/package.json index 71b032b1d88..e62edcbaad6 100644 --- a/e2e-playwright/test-plugins/grafana-test-datasource/package.json +++ b/e2e-playwright/test-plugins/grafana-test-datasource/package.json @@ -13,7 +13,7 @@ "devDependencies": { "@grafana/plugin-configs": "workspace:*", "@types/lodash": "4.17.7", - "@types/node": "22.15.0", + "@types/node": "24.9.2", "@types/prismjs": "1.26.4", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/package.json b/package.json index 503049f3ed2..f0f0e411591 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "@types/lodash": "4.17.20", "@types/logfmt": "^1.2.3", "@types/lucene": "^2", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/node-forge": "^1", "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.3.0", "@types/pluralize": "^0.0.33", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 950d5122932..c990c304caf 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -89,7 +89,7 @@ "@testing-library/react": "16.3.0", "@types/history": "4.7.11", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/papaparse": "5.3.16", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/packages/grafana-data/src/vector/FunctionalVector.ts b/packages/grafana-data/src/vector/FunctionalVector.ts index f5ccfc7be8a..a84b6122232 100644 --- a/packages/grafana-data/src/vector/FunctionalVector.ts +++ b/packages/grafana-data/src/vector/FunctionalVector.ts @@ -153,13 +153,13 @@ export abstract class FunctionalVector { findIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number { return this.toArray().findIndex(predicate, thisArg); } - entries(): IterableIterator<[number, T]> { + entries(): ArrayIterator<[number, T]> { return this.toArray().entries(); } - keys(): IterableIterator { + keys(): ArrayIterator { return this.toArray().keys(); } - values(): IterableIterator { + values(): ArrayIterator { return this.toArray().values(); } includes(searchElement: T, fromIndex?: number | undefined): boolean { diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 008f555cc3b..9a50bc2ee5c 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -40,7 +40,7 @@ }, "devDependencies": { "@rollup/plugin-node-resolve": "16.0.1", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/semver": "7.7.1", "esbuild": "0.25.8", "rimraf": "6.0.1", diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 3185d65d5c1..98cb22e9fb2 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -67,7 +67,7 @@ "@types/d3": "^7", "@types/jest": "^29.5.4", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/react-virtualized-auto-sizer": "1.0.8", "@types/tinycolor2": "1.4.6", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 5694659ab70..b79a584715f 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -35,7 +35,7 @@ "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "^29.5.4", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/systemjs": "6.15.3", "jest": "^29.6.4", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index c7413cdd517..4c8f16ac688 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -87,7 +87,7 @@ "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/pluralize": "^0.0.33", "@types/prismjs": "1.26.5", "esbuild": "0.25.8", diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 2ea7c0f82c7..db36dfec348 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -42,7 +42,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "^29.5.4", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/react-virtualized-auto-sizer": "1.0.8", diff --git a/packages/grafana-test-utils/package.json b/packages/grafana-test-utils/package.json index 70bf836f843..ecd8980173a 100644 --- a/packages/grafana-test-utils/package.json +++ b/packages/grafana-test-utils/package.json @@ -65,7 +65,7 @@ "@types/chance": "^1.1.7", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "jest": "29.7.0", "typescript": "5.9.2" } diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 91dc005eadc..6c96682758b 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -165,7 +165,7 @@ "@types/is-hotkey": "0.1.10", "@types/jest": "29.5.14", "@types/mock-raf": "1.0.6", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-color": "3.0.13", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index 363460331ab..778de26d31d 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -34,7 +34,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index 6671d62b3be..19ce293b786 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -35,7 +35,7 @@ "@types/debounce-promise": "3.1.9", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index 78dd4ab395d..b1106f0202c 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -23,7 +23,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "jest": "29.7.0", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index f5a2792f57c..8f4756784f8 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -27,7 +27,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index b4b36bb2a68..d738f47d24d 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -30,7 +30,7 @@ "@types/d3-random": "^3.0.2", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/uuid": "10.0.0", diff --git a/public/app/plugins/datasource/graphite/package.json b/public/app/plugins/datasource/graphite/package.json index 49df565f4fc..5804b4b1b48 100644 --- a/public/app/plugins/datasource/graphite/package.json +++ b/public/app/plugins/datasource/graphite/package.json @@ -31,7 +31,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/semver": "7.7.1", diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index a1ccd2a32bc..3fe1880cc50 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -31,7 +31,7 @@ "@types/jest": "29.5.14", "@types/lodash": "4.17.20", "@types/logfmt": "^1.2.3", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/react-window": "1.8.8", diff --git a/public/app/plugins/datasource/loki/package.json b/public/app/plugins/datasource/loki/package.json index 41c78cae48e..36f40009094 100644 --- a/public/app/plugins/datasource/loki/package.json +++ b/public/app/plugins/datasource/loki/package.json @@ -33,7 +33,7 @@ "@types/d3-random": "^3.0.2", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/uuid": "10.0.0", diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index d69d7c10eb0..9f3da85359b 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -24,7 +24,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "i18next-cli": "1.11.12", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index f7325cdd3d1..b541a1506be 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -23,7 +23,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "jest": "29.7.0", "ts-node": "10.9.2", diff --git a/public/app/plugins/datasource/parca/package.json b/public/app/plugins/datasource/parca/package.json index 09e627a75eb..b4b6041c348 100644 --- a/public/app/plugins/datasource/parca/package.json +++ b/public/app/plugins/datasource/parca/package.json @@ -23,7 +23,7 @@ "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "jest": "29.7.0", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index b3147dfd7b9..1a089ff5590 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -45,7 +45,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index c37f7d8d140..f7a8a8ad869 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -26,7 +26,7 @@ "@testing-library/react": "16.3.0", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "jest": "29.7.0", diff --git a/yarn.lock b/yarn.lock index 9c9e587d570..6479a1bc630 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2448,7 +2448,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -2492,7 +2492,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" jest: "npm:29.7.0" lodash: "npm:4.17.21" @@ -2523,7 +2523,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -2566,7 +2566,7 @@ __metadata: "@types/d3-random": "npm:^3.0.2" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/uuid": "npm:10.0.0" @@ -2608,7 +2608,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/semver": "npm:7.7.1" @@ -2651,7 +2651,7 @@ __metadata: "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" "@types/logfmt": "npm:^1.2.3" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/react-window": "npm:1.8.8" @@ -2696,7 +2696,7 @@ __metadata: "@types/d3-random": "npm:^3.0.2" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/uuid": "npm:10.0.0" @@ -2737,7 +2737,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" i18next-cli: "npm:1.11.12" lodash: "npm:4.17.21" @@ -2769,7 +2769,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" jest: "npm:29.7.0" lodash: "npm:4.17.21" @@ -2798,7 +2798,7 @@ __metadata: "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" jest: "npm:29.7.0" @@ -2837,7 +2837,7 @@ __metadata: "@types/debounce-promise": "npm:3.1.9" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -2890,7 +2890,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -2941,7 +2941,7 @@ __metadata: "@testing-library/react": "npm:16.3.0" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" jest: "npm:29.7.0" @@ -3074,7 +3074,7 @@ __metadata: "@types/d3-interpolate": "npm:^3.0.0" "@types/history": "npm:4.7.11" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/papaparse": "npm:5.3.16" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -3120,7 +3120,7 @@ __metadata: resolution: "@grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors" dependencies: "@rollup/plugin-node-resolve": "npm:16.0.1" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/semver": "npm:7.7.1" esbuild: "npm:0.25.8" rimraf: "npm:6.0.1" @@ -3226,7 +3226,7 @@ __metadata: "@types/d3": "npm:^7" "@types/jest": "npm:^29.5.4" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" "@types/react-virtualized-auto-sizer": "npm:1.0.8" "@types/tinycolor2": "npm:1.4.6" @@ -3347,7 +3347,7 @@ __metadata: "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:^29.5.4" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" "@types/systemjs": "npm:6.15.3" jest: "npm:^29.6.4" @@ -3469,7 +3469,7 @@ __metadata: "@types/debounce-promise": "npm:3.1.9" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/pluralize": "npm:^0.0.33" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" @@ -3662,7 +3662,7 @@ __metadata: "@testing-library/user-event": "npm:14.6.1" "@types/jest": "npm:^29.5.4" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" "@types/react-virtualized-auto-sizer": "npm:1.0.8" @@ -3698,7 +3698,7 @@ __metadata: "@types/chance": "npm:^1.1.7" "@types/jest": "npm:29.5.14" "@types/lodash": "npm:4.17.20" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" chance: "npm:^1.1.13" jest: "npm:29.7.0" jest-matcher-utils: "npm:29.7.0" @@ -3770,7 +3770,7 @@ __metadata: "@types/jquery": "npm:3.5.33" "@types/lodash": "npm:4.17.20" "@types/mock-raf": "npm:1.0.6" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/prismjs": "npm:1.26.5" "@types/react": "npm:18.3.18" "@types/react-color": "npm:3.0.13" @@ -4077,13 +4077,20 @@ __metadata: languageName: node linkType: hard -"@inquirer/figures@npm:^1.0.14, @inquirer/figures@npm:^1.0.3": +"@inquirer/figures@npm:^1.0.14": version: 1.0.14 resolution: "@inquirer/figures@npm:1.0.14" checksum: 10/39df361eb607cea5a020d457e25f9c6aee3a1de8975c6295a4b3bfe86ba7e7f7bfbefa6a52b145b1790f2690e5c8f10eb822e5bc764aff7ba00a6cd24eec5a25 languageName: node linkType: hard +"@inquirer/figures@npm:^1.0.3": + version: 1.0.11 + resolution: "@inquirer/figures@npm:1.0.11" + checksum: 10/357ddd2e83718bc3c9189d518b93fd69099af9c860354df9a5ac0ec024cb5df1228ae4608d2de7625624d2adcd047db813f29426a610eaae7b9e449f8c753c6b + languageName: node + linkType: hard + "@inquirer/input@npm:^4.2.5": version: 4.2.5 resolution: "@inquirer/input@npm:4.2.5" @@ -6049,11 +6056,11 @@ __metadata: linkType: hard "@openfeature/web-sdk@npm:^1.6.1": - version: 1.7.0 - resolution: "@openfeature/web-sdk@npm:1.7.0" + version: 1.6.2 + resolution: "@openfeature/web-sdk@npm:1.6.2" peerDependencies: "@openfeature/core": ^1.9.0 - checksum: 10/8b9f5ec5bb0e618b439b2e18b73d5c1aecdeea98da28588dc949da2fa0bd8258d27a8329f2371b527410bbeb858af4a8b01254fda59df9d10b26fb23e9217c22 + checksum: 10/0fcc0ef76ff51d4725a00ff07755b21941b647f775d1ff04fc3d973143e78c909ccba485f582fcbc7f6201f42c795c85bcb103c43d64924fedf13ea517f625ac languageName: node linkType: hard @@ -8956,7 +8963,7 @@ __metadata: languageName: node linkType: hard -"@swc/core@npm:1.13.19, @swc/core@npm:^1.10.8, @swc/core@npm:^1.5.22": +"@swc/core@npm:1.13.19": version: 1.13.19 resolution: "@swc/core@npm:1.13.19" dependencies: @@ -9002,7 +9009,7 @@ __metadata: languageName: node linkType: hard -"@swc/core@npm:1.13.3": +"@swc/core@npm:1.13.3, @swc/core@npm:^1.10.8, @swc/core@npm:^1.5.22": version: 1.13.3 resolution: "@swc/core@npm:1.13.3" dependencies: @@ -9116,7 +9123,7 @@ __metadata: "@grafana/schema": "workspace:*" "@grafana/ui": "workspace:*" "@types/lodash": "npm:4.17.7" - "@types/node": "npm:22.15.0" + "@types/node": "npm:24.9.2" "@types/prismjs": "npm:1.26.4" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -9148,7 +9155,7 @@ __metadata: "@grafana/schema": "workspace:*" "@grafana/ui": "workspace:*" "@types/lodash": "npm:4.17.7" - "@types/node": "npm:22.15.0" + "@types/node": "npm:24.9.2" "@types/prismjs": "npm:1.26.4" "@types/react": "npm:18.3.18" "@types/react-dom": "npm:18.3.5" @@ -10193,7 +10200,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:22.17.0, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=13.7.4": +"@types/node@npm:*, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=13.7.4": version: 22.17.0 resolution: "@types/node@npm:22.17.0" dependencies: @@ -10209,12 +10216,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:22.15.0": - version: 22.15.0 - resolution: "@types/node@npm:22.15.0" +"@types/node@npm:24.9.2": + version: 24.9.2 + resolution: "@types/node@npm:24.9.2" dependencies: - undici-types: "npm:~6.21.0" - checksum: 10/96911bc8bcebd32e96d0d6b46214f5b935e47ff3dac1fc6cf6a5256ebdd01dfecf689a41b3e3a1f35385c3341e0b79e0cf42a6409ce81e6b5fb9b3d7317fedbb + undici-types: "npm:~7.16.0" + checksum: 10/3e76ad89cca317c0886deedab0245b6b2a04ef6c47362bd3918020296f3e9630334795af9cee8c6633eae774c85d848ff2e6bed5a7c3133fc94968364fc3ee36 languageName: node linkType: hard @@ -13153,7 +13160,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:5.6.2, chalk@npm:^5.2.0, chalk@npm:^5.3.0, chalk@npm:^5.4.1, chalk@npm:^5.6.2": +"chalk@npm:5.6.2, chalk@npm:^5.6.2": version: 5.6.2 resolution: "chalk@npm:5.6.2" checksum: 10/1b2f48f6fba1370670d5610f9cd54c391d6ede28f4b7062dd38244ea5768777af72e5be6b74fb6c6d54cb84c4a2dff3f3afa9b7cb5948f7f022cfd3d087989e0 @@ -13191,6 +13198,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.2.0, chalk@npm:^5.3.0, chalk@npm:^5.4.1": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: 10/29df3ffcdf25656fed6e95962e2ef86d14dfe03cd50e7074b06bad9ffbbf6089adbb40f75c00744d843685c8d008adaf3aed31476780312553caf07fa86e5bc7 + languageName: node + linkType: hard + "chance@npm:^1.1.13": version: 1.1.13 resolution: "chance@npm:1.1.13" @@ -13794,7 +13808,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:14.0.1, commander@npm:~14.0.0": +"commander@npm:14.0.1": version: 14.0.1 resolution: "commander@npm:14.0.1" checksum: 10/783115e9403caeca29c0fcbd4e0358f70c67760e4e4933f3453fcdd5ddba2ec44173c8da5213d7ce5e404f51c7e71203a42c548164dbe27b668b32a8981577f1 @@ -13871,6 +13885,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:~14.0.0": + version: 14.0.0 + resolution: "commander@npm:14.0.0" + checksum: 10/c05418bfc35a3e8b5c67bd9f75f5b773f386f9b85f83e70e7c926047f270929cb06cf13cd68f387dd6e7e23c6157de8171b28ba606abd3e6256028f1f789becf + languageName: node + linkType: hard + "comment-parser@npm:1.4.1": version: 1.4.1 resolution: "comment-parser@npm:1.4.1" @@ -16109,7 +16130,16 @@ __metadata: languageName: node linkType: hard -"enquirer@npm:^2.3.6, enquirer@npm:^2.4.1": +"enquirer@npm:^2.3.6, enquirer@npm:~2.3.6": + version: 2.3.6 + resolution: "enquirer@npm:2.3.6" + dependencies: + ansi-colors: "npm:^4.1.1" + checksum: 10/751d14f037eb7683997e696fb8d5fe2675e0b0cde91182c128cf598acf3f5bd9005f35f7c2a9109e291140af496ebec237b6dac86067d59a9b44f3688107f426 + languageName: node + linkType: hard + +"enquirer@npm:^2.4.1": version: 2.4.1 resolution: "enquirer@npm:2.4.1" dependencies: @@ -16119,15 +16149,6 @@ __metadata: languageName: node linkType: hard -"enquirer@npm:~2.3.6": - version: 2.3.6 - resolution: "enquirer@npm:2.3.6" - dependencies: - ansi-colors: "npm:^4.1.1" - checksum: 10/751d14f037eb7683997e696fb8d5fe2675e0b0cde91182c128cf598acf3f5bd9005f35f7c2a9109e291140af496ebec237b6dac86067d59a9b44f3688107f426 - languageName: node - linkType: hard - "ensure-posix-path@npm:^1.1.0": version: 1.1.1 resolution: "ensure-posix-path@npm:1.1.1" @@ -18882,7 +18903,7 @@ __metadata: "@types/lodash": "npm:4.17.20" "@types/logfmt": "npm:^1.2.3" "@types/lucene": "npm:^2" - "@types/node": "npm:22.17.0" + "@types/node": "npm:24.9.2" "@types/node-forge": "npm:^1" "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.3.0" "@types/pluralize": "npm:^0.0.33" @@ -32578,6 +32599,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~7.16.0": + version: 7.16.0 + resolution: "undici-types@npm:7.16.0" + checksum: 10/db43439f69c2d94cc29f75cbfe9de86df87061d6b0c577ebe9bb3255f49b22c50162a7d7eb413b0458b6510b8ca299ac7cff38c3a29fbd31af9f504bcf7fbc0d + languageName: node + linkType: hard + "undici@npm:^7.12.0": version: 7.13.0 resolution: "undici@npm:7.13.0" From 0124aab8050011517674a765a587ae2d11d3bd8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 20:08:38 +0300 Subject: [PATCH 23/72] deps(actions): bump trufflesecurity/trufflehog from 3.90.11 to 3.90.12 (#113069) Bumps [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog) from 3.90.11 to 3.90.12. - [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/ad6fc8fb446b8fafbf7ea8193d2d6bfd42f45690...b84c3d14d189e16da175e2c27fa8136603783ffc) --- updated-dependencies: - dependency-name: trufflesecurity/trufflehog dependency-version: 3.90.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 7278c8ad3ed..c5bc7fd095e 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@ad6fc8fb446b8fafbf7ea8193d2d6bfd42f45690 # v3.90.11 + uses: trufflesecurity/trufflehog@b84c3d14d189e16da175e2c27fa8136603783ffc # v3.90.12 with: extra_args: --results=verified From 6a3e95913ef091bf97492d98ee2db5a68df1fbb2 Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Wed, 29 Oct 2025 18:15:16 +0100 Subject: [PATCH 24/72] Scenes: Fix timezone not being preserved in links (#112879) * Scenes: Fix timezone not being preserved in links * Update E2E --- .../dashboard-templating.spec.ts | 9 ++------ package.json | 4 ++-- yarn.lock | 22 +++++++++---------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/e2e-playwright/dashboards-suite/dashboard-templating.spec.ts b/e2e-playwright/dashboards-suite/dashboard-templating.spec.ts index 1b3f8a326fa..ded0abd61ef 100644 --- a/e2e-playwright/dashboards-suite/dashboard-templating.spec.ts +++ b/e2e-playwright/dashboards-suite/dashboard-templating.spec.ts @@ -19,9 +19,7 @@ test.describe( // Open dashboard global variables and interpolation await gotoDashboardPage({ uid: DASHBOARD_UID }); - // Get the actual timezone from the browser context (should be Pacific/Easter due to test.use) - const timeZone = await page.evaluate(() => Intl.DateTimeFormat().resolvedOptions().timeZone); - const example = `Example: from=now-6h&to=now&timezone=${encodeURIComponent(timeZone)}`; + const example = `Example: from=now-6h&to=now&timezone=browser`; const expectedItems: string[] = [ '__dashboard = Templating - Global variables and interpolation', @@ -68,10 +66,7 @@ test.describe( // Check link interpolation is working correctly const exampleLink = page.locator(`a:has-text("${example}")`); - await expect(exampleLink).toHaveAttribute( - 'href', - `https://example.com/?from=now-6h&to=now&timezone=${encodeURIComponent(timeZone)}` - ); + await expect(exampleLink).toHaveAttribute('href', `https://example.com/?from=now-6h&to=now&timezone=browser`); }); } ); diff --git a/package.json b/package.json index f0f0e411591..d74e091448d 100644 --- a/package.json +++ b/package.json @@ -296,8 +296,8 @@ "@grafana/plugin-ui": "^0.10.10", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^6.39.8", - "@grafana/scenes-react": "^6.39.8", + "@grafana/scenes": "^6.40.1", + "@grafana/scenes-react": "^6.40.1", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/yarn.lock b/yarn.lock index 6479a1bc630..7480476abed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3555,11 +3555,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:^6.39.8": - version: 6.39.8 - resolution: "@grafana/scenes-react@npm:6.39.8" +"@grafana/scenes-react@npm:^6.40.1": + version: 6.40.1 + resolution: "@grafana/scenes-react@npm:6.40.1" dependencies: - "@grafana/scenes": "npm:6.39.8" + "@grafana/scenes": "npm:6.40.1" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3571,7 +3571,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/14f94437976890e5f9f2046be67796612273041f45ac76baace296249630e1189b28838ff6346c4463fa3fabf3e55d34ccbc4ff92a5ece83bf04566d52638c87 + checksum: 10/4515d53609d7b49e234bbc5c3da8131406c20b8ac0e54b1a5ca3ce3d1d42220ff08db65d31c83c9796188ef9d76e9a24fef170badb793a56b44bbabd31302575 languageName: node linkType: hard @@ -3601,9 +3601,9 @@ __metadata: languageName: node linkType: hard -"@grafana/scenes@npm:6.39.8, @grafana/scenes@npm:^6.39.8": - version: 6.39.8 - resolution: "@grafana/scenes@npm:6.39.8" +"@grafana/scenes@npm:6.40.1, @grafana/scenes@npm:^6.40.1": + version: 6.40.1 + resolution: "@grafana/scenes@npm:6.40.1" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3623,7 +3623,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/0a9ce2dfae242c805463867d65f6dbd434490c764f791bb923af47b3042978ffceccbfe52e79d940b89c1e62f86a39992c10b505eb330dcf73005f69f2c2a444 + checksum: 10/f3f33d58bca9d05cf8d9527564e2f6bd9f164b44531382972c2b1ed0a54f54e29a6130fcab5797cd5dbba673e5d678ae5d1ef9c01ce6fe7895ee0f0c9408bc10 languageName: node linkType: hard @@ -18832,8 +18832,8 @@ __metadata: "@grafana/plugin-ui": "npm:^0.10.10" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:^6.39.8" - "@grafana/scenes-react": "npm:^6.39.8" + "@grafana/scenes": "npm:^6.40.1" + "@grafana/scenes-react": "npm:^6.40.1" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/test-utils": "workspace:*" From de88abafdd955d71d931b70c24dfe0c4b5a25b50 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:32:31 -0400 Subject: [PATCH 25/72] Plugins API: Merge meta and installs (#112962) --- apps/plugins/go.mod | 6 +- apps/plugins/go.sum | 4 - apps/plugins/kinds/manifest.cue | 5 +- .../kinds/{pluginmeta.cue => plugin.cue} | 26 +- apps/plugins/kinds/plugininstall.cue | 15 - .../plugins/v0alpha1/plugin_client_gen.go | 123 ++++ ...nmeta_codec_gen.go => plugin_codec_gen.go} | 10 +- .../plugin_getmeta_response_types_gen.go | 463 +++++++++++++ ...metadata_gen.go => plugin_metadata_gen.go} | 8 +- ...eta_object_gen.go => plugin_object_gen.go} | 102 +-- .../plugins/v0alpha1/plugin_schema_gen.go | 34 + .../apis/plugins/v0alpha1/plugin_spec_gen.go | 25 + ...eta_status_gen.go => plugin_status_gen.go} | 28 +- .../v0alpha1/plugininstall_client_gen.go | 99 --- .../v0alpha1/plugininstall_codec_gen.go | 28 - .../v0alpha1/plugininstall_metadata_gen.go | 31 - .../v0alpha1/plugininstall_object_gen.go | 319 --------- .../v0alpha1/plugininstall_schema_gen.go | 34 - .../v0alpha1/plugininstall_spec_gen.go | 25 - .../v0alpha1/plugininstall_status_gen.go | 44 -- .../plugins/v0alpha1/pluginmeta_client_gen.go | 99 --- .../plugins/v0alpha1/pluginmeta_schema_gen.go | 34 - .../plugins/v0alpha1/pluginmeta_spec_gen.go | 477 -------------- apps/plugins/pkg/apis/plugins_manifest.go | 253 ++++++- apps/plugins/pkg/app/app.go | 43 +- apps/plugins/pkg/app/install/registrar.go | 16 +- apps/plugins/pkg/app/storage.go | 75 --- pkg/registry/apps/plugins/register.go | 25 +- .../pluginsintegration/installsync/syncer.go | 6 + .../installsync/syncer_test.go | 620 ++++++++---------- pkg/tests/apis/plugins/discovery_test.go | 48 +- pkg/tests/apis/plugins/plugininstalls_test.go | 185 +----- pkg/tests/apis/plugins/pluginsmeta_test.go | 47 -- 33 files changed, 1350 insertions(+), 2007 deletions(-) rename apps/plugins/kinds/{pluginmeta.cue => plugin.cue} (92%) delete mode 100644 apps/plugins/kinds/plugininstall.cue create mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/plugin_client_gen.go rename apps/plugins/pkg/apis/plugins/v0alpha1/{pluginmeta_codec_gen.go => plugin_codec_gen.go} (56%) create mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/plugin_getmeta_response_types_gen.go rename apps/plugins/pkg/apis/plugins/v0alpha1/{pluginmeta_metadata_gen.go => plugin_metadata_gen.go} (85%) rename apps/plugins/pkg/apis/plugins/v0alpha1/{pluginmeta_object_gen.go => plugin_object_gen.go} (69%) create mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/plugin_schema_gen.go create mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/plugin_spec_gen.go rename apps/plugins/pkg/apis/plugins/v0alpha1/{pluginmeta_status_gen.go => plugin_status_gen.go} (52%) delete mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_client_gen.go delete mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go delete mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go delete mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go delete mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go delete mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go delete mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go delete mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_client_gen.go delete mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go delete mode 100644 apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go delete mode 100644 apps/plugins/pkg/app/storage.go delete mode 100644 pkg/tests/apis/plugins/pluginsmeta_test.go diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod index ab1de1b4186..f0ef53718bc 100644 --- a/apps/plugins/go.mod +++ b/apps/plugins/go.mod @@ -3,8 +3,8 @@ module github.com/grafana/grafana/apps/plugins go 1.25.3 require ( - github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37 github.com/grafana/grafana-app-sdk v0.48.1 + github.com/grafana/grafana-app-sdk/logging v0.48.1 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde k8s.io/apimachinery v0.34.1 k8s.io/apiserver v0.34.1 @@ -14,7 +14,6 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/blang/semver/v4 v4.0.0 // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -37,8 +36,8 @@ require ( github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // 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/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect - github.com/grafana/grafana-app-sdk/logging v0.48.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -93,7 +92,6 @@ require ( k8s.io/api v0.34.1 // indirect k8s.io/apiextensions-apiserver v0.34.1 // indirect k8s.io/client-go v0.34.1 // indirect - k8s.io/component-base v0.34.1 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum index 07264ead5aa..fb208331835 100644 --- a/apps/plugins/go.sum +++ b/apps/plugins/go.sum @@ -1,7 +1,5 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -242,8 +240,6 @@ k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= -k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= -k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= diff --git a/apps/plugins/kinds/manifest.cue b/apps/plugins/kinds/manifest.cue index f16ba2d1a72..b3f5251432b 100644 --- a/apps/plugins/kinds/manifest.cue +++ b/apps/plugins/kinds/manifest.cue @@ -5,14 +5,13 @@ manifest: { groupOverride: "plugins.grafana.app" versions: { "v0alpha1": { - served: false + served: true codegen: { ts: {enabled: false} go: {enabled: true} } kinds: [ - pluginMetaV0Alpha1, - pluginInstallV0Alpha1, + pluginV0Alpha1, ] } } diff --git a/apps/plugins/kinds/pluginmeta.cue b/apps/plugins/kinds/plugin.cue similarity index 92% rename from apps/plugins/kinds/pluginmeta.cue rename to apps/plugins/kinds/plugin.cue index 93f41095707..4f381082969 100644 --- a/apps/plugins/kinds/pluginmeta.cue +++ b/apps/plugins/kinds/plugin.cue @@ -4,17 +4,33 @@ import ( "time" ) -pluginMetaV0Alpha1: { - kind: "PluginMeta" - plural: "pluginsmeta" +pluginV0Alpha1: { + kind: "Plugin" + plural: "plugins" scope: "Namespaced" schema: { spec: { - pluginJSON: #JSONData, + id: string + version: string + url?: string + class: "core" | "external" | "cdn" + } + } + routes: { + "/meta": { + "GET": { + request: {} + response: #JSONData, + responseMetadata: { + typeMeta: false + objectMeta: false + } + } } } } + // JSON configuration schema for Grafana plugins // Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json #JSONData: { @@ -217,4 +233,4 @@ pluginMetaV0Alpha1: { title?: string description?: string }] -} +} \ No newline at end of file diff --git a/apps/plugins/kinds/plugininstall.cue b/apps/plugins/kinds/plugininstall.cue deleted file mode 100644 index d72cfac7342..00000000000 --- a/apps/plugins/kinds/plugininstall.cue +++ /dev/null @@ -1,15 +0,0 @@ -package plugins - -pluginInstallV0Alpha1: { - kind: "PluginInstall" - plural: "plugininstalls" - scope: "Namespaced" - schema: { - spec: { - id: string - version: string - url?: string - class: "core" | "external" | "cdn" - } - } -} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_client_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_client_gen.go new file mode 100644 index 00000000000..3e9bcb3a2bb --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_client_gen.go @@ -0,0 +1,123 @@ +package v0alpha1 + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type PluginClient struct { + client *resource.TypedClient[*Plugin, *PluginList] +} + +func NewPluginClient(client resource.Client) *PluginClient { + return &PluginClient{ + client: resource.NewTypedClient[*Plugin, *PluginList](client, PluginKind()), + } +} + +func NewPluginClientFromGenerator(generator resource.ClientGenerator) (*PluginClient, error) { + c, err := generator.ClientFor(PluginKind()) + if err != nil { + return nil, err + } + return NewPluginClient(c), nil +} + +func (c *PluginClient) Get(ctx context.Context, identifier resource.Identifier) (*Plugin, error) { + return c.client.Get(ctx, identifier) +} + +func (c *PluginClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginList, error) { + return c.client.List(ctx, namespace, opts) +} + +func (c *PluginClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginList, error) { + resp, err := c.client.List(ctx, namespace, resource.ListOptions{ + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + for resp.GetContinue() != "" { + page, err := c.client.List(ctx, namespace, resource.ListOptions{ + Continue: resp.GetContinue(), + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + resp.SetContinue(page.GetContinue()) + resp.SetResourceVersion(page.GetResourceVersion()) + resp.SetItems(append(resp.GetItems(), page.GetItems()...)) + } + return resp, nil +} + +func (c *PluginClient) Create(ctx context.Context, obj *Plugin, opts resource.CreateOptions) (*Plugin, error) { + // Make sure apiVersion and kind are set + obj.APIVersion = GroupVersion.Identifier() + obj.Kind = PluginKind().Kind() + return c.client.Create(ctx, obj, opts) +} + +func (c *PluginClient) Update(ctx context.Context, obj *Plugin, opts resource.UpdateOptions) (*Plugin, error) { + return c.client.Update(ctx, obj, opts) +} + +func (c *PluginClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Plugin, error) { + return c.client.Patch(ctx, identifier, req, opts) +} + +func (c *PluginClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus PluginStatus, opts resource.UpdateOptions) (*Plugin, error) { + return c.client.Update(ctx, &Plugin{ + TypeMeta: metav1.TypeMeta{ + Kind: PluginKind().Kind(), + APIVersion: GroupVersion.Identifier(), + }, + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: opts.ResourceVersion, + Namespace: identifier.Namespace, + Name: identifier.Name, + }, + Status: newStatus, + }, resource.UpdateOptions{ + Subresource: "status", + ResourceVersion: opts.ResourceVersion, + }) +} + +func (c *PluginClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { + return c.client.Delete(ctx, identifier, opts) +} + +type GetMetaRequest struct { + Headers http.Header +} + +func (c *PluginClient) GetMeta(ctx context.Context, identifier resource.Identifier, request GetMetaRequest) (*GetMeta, error) { + resp, err := c.client.SubresourceRequest(ctx, identifier, resource.CustomRouteRequestOptions{ + Path: "/meta", + Verb: "GET", + Headers: request.Headers, + }) + if err != nil { + return nil, err + } + cast := GetMeta{} + err = json.Unmarshal(resp, &cast) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal response bytes into GetMeta: %w", err) + } + return &cast, nil +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_codec_gen.go similarity index 56% rename from apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go rename to apps/plugins/pkg/apis/plugins/v0alpha1/plugin_codec_gen.go index 77fb6f918a2..c8fe78b00f1 100644 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_codec_gen.go @@ -11,18 +11,18 @@ import ( "github.com/grafana/grafana-app-sdk/resource" ) -// PluginMetaJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding -type PluginMetaJSONCodec struct{} +// PluginJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type PluginJSONCodec struct{} // Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` -func (*PluginMetaJSONCodec) Read(reader io.Reader, into resource.Object) error { +func (*PluginJSONCodec) Read(reader io.Reader, into resource.Object) error { return json.NewDecoder(reader).Decode(into) } // Write writes JSON-encoded bytes into `writer` marshaled from `from` -func (*PluginMetaJSONCodec) Write(writer io.Writer, from resource.Object) error { +func (*PluginJSONCodec) Write(writer io.Writer, from resource.Object) error { return json.NewEncoder(writer).Encode(from) } // Interface compliance checks -var _ resource.Codec = &PluginMetaJSONCodec{} +var _ resource.Codec = &PluginJSONCodec{} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_getmeta_response_types_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_getmeta_response_types_gen.go new file mode 100644 index 00000000000..6b0e6899a5b --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_getmeta_response_types_gen.go @@ -0,0 +1,463 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + time "time" +) + +// +k8s:openapi-gen=true +type Info struct { + // Required fields + // +listType=set + Keywords []string `json:"keywords"` + Logos V0alpha1InfoLogos `json:"logos"` + Updated time.Time `json:"updated"` + Version string `json:"version"` + // Optional fields + Author *V0alpha1InfoAuthor `json:"author,omitempty"` + Description *string `json:"description,omitempty"` + // +listType=atomic + Links []V0alpha1InfoLinks `json:"links,omitempty"` + // +listType=atomic + Screenshots []V0alpha1InfoScreenshots `json:"screenshots,omitempty"` +} + +// NewInfo creates a new Info object. +func NewInfo() *Info { + return &Info{ + Keywords: []string{}, + Logos: *NewV0alpha1InfoLogos(), + } +} + +// +k8s:openapi-gen=true +type Dependencies struct { + // Required field + GrafanaDependency string `json:"grafanaDependency"` + // Optional fields + GrafanaVersion *string `json:"grafanaVersion,omitempty"` + // +listType=set + // +listMapKey=id + Plugins []V0alpha1DependenciesPlugins `json:"plugins,omitempty"` + Extensions *V0alpha1DependenciesExtensions `json:"extensions,omitempty"` +} + +// NewDependencies creates a new Dependencies object. +func NewDependencies() *Dependencies { + return &Dependencies{} +} + +// +k8s:openapi-gen=true +type EnterpriseFeatures struct { + // Allow additional properties + HealthDiagnosticsErrors *bool `json:"healthDiagnosticsErrors,omitempty"` +} + +// NewEnterpriseFeatures creates a new EnterpriseFeatures object. +func NewEnterpriseFeatures() *EnterpriseFeatures { + return &EnterpriseFeatures{ + HealthDiagnosticsErrors: (func(input bool) *bool { return &input })(false), + } +} + +// +k8s:openapi-gen=true +type Include struct { + Uid *string `json:"uid,omitempty"` + Type *IncludeType `json:"type,omitempty"` + Name *string `json:"name,omitempty"` + Component *string `json:"component,omitempty"` + Role *IncludeRole `json:"role,omitempty"` + Action *string `json:"action,omitempty"` + Path *string `json:"path,omitempty"` + AddToNav *bool `json:"addToNav,omitempty"` + DefaultNav *bool `json:"defaultNav,omitempty"` + Icon *string `json:"icon,omitempty"` +} + +// NewInclude creates a new Include object. +func NewInclude() *Include { + return &Include{} +} + +// +k8s:openapi-gen=true +type QueryOptions struct { + MaxDataPoints *bool `json:"maxDataPoints,omitempty"` + MinInterval *bool `json:"minInterval,omitempty"` + CacheTimeout *bool `json:"cacheTimeout,omitempty"` +} + +// NewQueryOptions creates a new QueryOptions object. +func NewQueryOptions() *QueryOptions { + return &QueryOptions{} +} + +// +k8s:openapi-gen=true +type Route struct { + Path *string `json:"path,omitempty"` + Method *string `json:"method,omitempty"` + Url *string `json:"url,omitempty"` + ReqSignedIn *bool `json:"reqSignedIn,omitempty"` + ReqRole *string `json:"reqRole,omitempty"` + ReqAction *string `json:"reqAction,omitempty"` + // +listType=atomic + Headers []string `json:"headers,omitempty"` + Body map[string]interface{} `json:"body,omitempty"` + TokenAuth *V0alpha1RouteTokenAuth `json:"tokenAuth,omitempty"` + JwtTokenAuth *V0alpha1RouteJwtTokenAuth `json:"jwtTokenAuth,omitempty"` + // +listType=atomic + UrlParams []V0alpha1RouteUrlParams `json:"urlParams,omitempty"` +} + +// NewRoute creates a new Route object. +func NewRoute() *Route { + return &Route{} +} + +// +k8s:openapi-gen=true +type IAM struct { + // +listType=atomic + Permissions []V0alpha1IAMPermissions `json:"permissions,omitempty"` +} + +// NewIAM creates a new IAM object. +func NewIAM() *IAM { + return &IAM{} +} + +// +k8s:openapi-gen=true +type Role struct { + Role *V0alpha1RoleRole `json:"role,omitempty"` + // +listType=set + Grants []string `json:"grants,omitempty"` +} + +// NewRole creates a new Role object. +func NewRole() *Role { + return &Role{} +} + +// +k8s:openapi-gen=true +type Extensions struct { + // +listType=atomic + AddedComponents []V0alpha1ExtensionsAddedComponents `json:"addedComponents,omitempty"` + // +listType=atomic + AddedLinks []V0alpha1ExtensionsAddedLinks `json:"addedLinks,omitempty"` + // +listType=set + // +listMapKey=id + ExposedComponents []V0alpha1ExtensionsExposedComponents `json:"exposedComponents,omitempty"` + // +listType=set + // +listMapKey=id + ExtensionPoints []V0alpha1ExtensionsExtensionPoints `json:"extensionPoints,omitempty"` +} + +// NewExtensions creates a new Extensions object. +func NewExtensions() *Extensions { + return &Extensions{} +} + +// +k8s:openapi-gen=true +type GetMeta struct { + // Unique name of the plugin + Id string `json:"id"` + // Plugin type + Type GetMetaType `json:"type"` + // Human-readable name of the plugin + Name string `json:"name"` + // Metadata for the plugin + Info Info `json:"info"` + // Dependency information + Dependencies Dependencies `json:"dependencies"` + // Optional fields + Alerting *bool `json:"alerting,omitempty"` + Annotations *bool `json:"annotations,omitempty"` + AutoEnabled *bool `json:"autoEnabled,omitempty"` + Backend *bool `json:"backend,omitempty"` + BuildMode *string `json:"buildMode,omitempty"` + BuiltIn *bool `json:"builtIn,omitempty"` + Category *GetMetaCategory `json:"category,omitempty"` + EnterpriseFeatures *EnterpriseFeatures `json:"enterpriseFeatures,omitempty"` + Executable *string `json:"executable,omitempty"` + HideFromList *bool `json:"hideFromList,omitempty"` + // +listType=atomic + Includes []Include `json:"includes,omitempty"` + Logs *bool `json:"logs,omitempty"` + Metrics *bool `json:"metrics,omitempty"` + MultiValueFilterOperators *bool `json:"multiValueFilterOperators,omitempty"` + PascalName *string `json:"pascalName,omitempty"` + Preload *bool `json:"preload,omitempty"` + QueryOptions *QueryOptions `json:"queryOptions,omitempty"` + // +listType=atomic + Routes []Route `json:"routes,omitempty"` + SkipDataQuery *bool `json:"skipDataQuery,omitempty"` + State *GetMetaState `json:"state,omitempty"` + Streaming *bool `json:"streaming,omitempty"` + Tracing *bool `json:"tracing,omitempty"` + Iam *IAM `json:"iam,omitempty"` + // +listType=atomic + Roles []Role `json:"roles,omitempty"` + Extensions *Extensions `json:"extensions,omitempty"` +} + +// NewGetMeta creates a new GetMeta object. +func NewGetMeta() *GetMeta { + return &GetMeta{ + Info: *NewInfo(), + Dependencies: *NewDependencies(), + } +} + +// +k8s:openapi-gen=true +type V0alpha1InfoLogos struct { + Small string `json:"small"` + Large string `json:"large"` +} + +// NewV0alpha1InfoLogos creates a new V0alpha1InfoLogos object. +func NewV0alpha1InfoLogos() *V0alpha1InfoLogos { + return &V0alpha1InfoLogos{} +} + +// +k8s:openapi-gen=true +type V0alpha1InfoAuthor struct { + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` + Url *string `json:"url,omitempty"` +} + +// NewV0alpha1InfoAuthor creates a new V0alpha1InfoAuthor object. +func NewV0alpha1InfoAuthor() *V0alpha1InfoAuthor { + return &V0alpha1InfoAuthor{} +} + +// +k8s:openapi-gen=true +type V0alpha1InfoLinks struct { + Name *string `json:"name,omitempty"` + Url *string `json:"url,omitempty"` +} + +// NewV0alpha1InfoLinks creates a new V0alpha1InfoLinks object. +func NewV0alpha1InfoLinks() *V0alpha1InfoLinks { + return &V0alpha1InfoLinks{} +} + +// +k8s:openapi-gen=true +type V0alpha1InfoScreenshots struct { + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` +} + +// NewV0alpha1InfoScreenshots creates a new V0alpha1InfoScreenshots object. +func NewV0alpha1InfoScreenshots() *V0alpha1InfoScreenshots { + return &V0alpha1InfoScreenshots{} +} + +// +k8s:openapi-gen=true +type V0alpha1DependenciesPlugins struct { + Id string `json:"id"` + Type V0alpha1DependenciesPluginsType `json:"type"` + Name string `json:"name"` +} + +// NewV0alpha1DependenciesPlugins creates a new V0alpha1DependenciesPlugins object. +func NewV0alpha1DependenciesPlugins() *V0alpha1DependenciesPlugins { + return &V0alpha1DependenciesPlugins{} +} + +// +k8s:openapi-gen=true +type V0alpha1DependenciesExtensions struct { + // +listType=set + ExposedComponents []string `json:"exposedComponents,omitempty"` +} + +// NewV0alpha1DependenciesExtensions creates a new V0alpha1DependenciesExtensions object. +func NewV0alpha1DependenciesExtensions() *V0alpha1DependenciesExtensions { + return &V0alpha1DependenciesExtensions{} +} + +// +k8s:openapi-gen=true +type V0alpha1RouteTokenAuth struct { + Url *string `json:"url,omitempty"` + // +listType=set + Scopes []string `json:"scopes,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// NewV0alpha1RouteTokenAuth creates a new V0alpha1RouteTokenAuth object. +func NewV0alpha1RouteTokenAuth() *V0alpha1RouteTokenAuth { + return &V0alpha1RouteTokenAuth{} +} + +// +k8s:openapi-gen=true +type V0alpha1RouteJwtTokenAuth struct { + Url *string `json:"url,omitempty"` + // +listType=set + Scopes []string `json:"scopes,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// NewV0alpha1RouteJwtTokenAuth creates a new V0alpha1RouteJwtTokenAuth object. +func NewV0alpha1RouteJwtTokenAuth() *V0alpha1RouteJwtTokenAuth { + return &V0alpha1RouteJwtTokenAuth{} +} + +// +k8s:openapi-gen=true +type V0alpha1RouteUrlParams struct { + Name *string `json:"name,omitempty"` + Content *string `json:"content,omitempty"` +} + +// NewV0alpha1RouteUrlParams creates a new V0alpha1RouteUrlParams object. +func NewV0alpha1RouteUrlParams() *V0alpha1RouteUrlParams { + return &V0alpha1RouteUrlParams{} +} + +// +k8s:openapi-gen=true +type V0alpha1IAMPermissions struct { + Action *string `json:"action,omitempty"` + Scope *string `json:"scope,omitempty"` +} + +// NewV0alpha1IAMPermissions creates a new V0alpha1IAMPermissions object. +func NewV0alpha1IAMPermissions() *V0alpha1IAMPermissions { + return &V0alpha1IAMPermissions{} +} + +// +k8s:openapi-gen=true +type V0alpha1RoleRolePermissions struct { + Action *string `json:"action,omitempty"` + Scope *string `json:"scope,omitempty"` +} + +// NewV0alpha1RoleRolePermissions creates a new V0alpha1RoleRolePermissions object. +func NewV0alpha1RoleRolePermissions() *V0alpha1RoleRolePermissions { + return &V0alpha1RoleRolePermissions{} +} + +// +k8s:openapi-gen=true +type V0alpha1RoleRole struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + // +listType=atomic + Permissions []V0alpha1RoleRolePermissions `json:"permissions,omitempty"` +} + +// NewV0alpha1RoleRole creates a new V0alpha1RoleRole object. +func NewV0alpha1RoleRole() *V0alpha1RoleRole { + return &V0alpha1RoleRole{} +} + +// +k8s:openapi-gen=true +type V0alpha1ExtensionsAddedComponents struct { + // +listType=set + Targets []string `json:"targets"` + Title string `json:"title"` + Description *string `json:"description,omitempty"` +} + +// NewV0alpha1ExtensionsAddedComponents creates a new V0alpha1ExtensionsAddedComponents object. +func NewV0alpha1ExtensionsAddedComponents() *V0alpha1ExtensionsAddedComponents { + return &V0alpha1ExtensionsAddedComponents{ + Targets: []string{}, + } +} + +// +k8s:openapi-gen=true +type V0alpha1ExtensionsAddedLinks struct { + // +listType=set + Targets []string `json:"targets"` + Title string `json:"title"` + Description *string `json:"description,omitempty"` +} + +// NewV0alpha1ExtensionsAddedLinks creates a new V0alpha1ExtensionsAddedLinks object. +func NewV0alpha1ExtensionsAddedLinks() *V0alpha1ExtensionsAddedLinks { + return &V0alpha1ExtensionsAddedLinks{ + Targets: []string{}, + } +} + +// +k8s:openapi-gen=true +type V0alpha1ExtensionsExposedComponents struct { + Id string `json:"id"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` +} + +// NewV0alpha1ExtensionsExposedComponents creates a new V0alpha1ExtensionsExposedComponents object. +func NewV0alpha1ExtensionsExposedComponents() *V0alpha1ExtensionsExposedComponents { + return &V0alpha1ExtensionsExposedComponents{} +} + +// +k8s:openapi-gen=true +type V0alpha1ExtensionsExtensionPoints struct { + Id string `json:"id"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` +} + +// NewV0alpha1ExtensionsExtensionPoints creates a new V0alpha1ExtensionsExtensionPoints object. +func NewV0alpha1ExtensionsExtensionPoints() *V0alpha1ExtensionsExtensionPoints { + return &V0alpha1ExtensionsExtensionPoints{} +} + +// +k8s:openapi-gen=true +type IncludeType string + +const ( + IncludeTypeDashboard IncludeType = "dashboard" + IncludeTypePage IncludeType = "page" + IncludeTypePanel IncludeType = "panel" + IncludeTypeDatasource IncludeType = "datasource" +) + +// +k8s:openapi-gen=true +type IncludeRole string + +const ( + IncludeRoleAdmin IncludeRole = "Admin" + IncludeRoleEditor IncludeRole = "Editor" + IncludeRoleViewer IncludeRole = "Viewer" +) + +// +k8s:openapi-gen=true +type GetMetaType string + +const ( + GetMetaTypeApp GetMetaType = "app" + GetMetaTypeDatasource GetMetaType = "datasource" + GetMetaTypePanel GetMetaType = "panel" + GetMetaTypeRenderer GetMetaType = "renderer" +) + +// +k8s:openapi-gen=true +type GetMetaCategory string + +const ( + GetMetaCategoryTsdb GetMetaCategory = "tsdb" + GetMetaCategoryLogging GetMetaCategory = "logging" + GetMetaCategoryCloud GetMetaCategory = "cloud" + GetMetaCategoryTracing GetMetaCategory = "tracing" + GetMetaCategoryProfiling GetMetaCategory = "profiling" + GetMetaCategorySql GetMetaCategory = "sql" + GetMetaCategoryEnterprise GetMetaCategory = "enterprise" + GetMetaCategoryIot GetMetaCategory = "iot" + GetMetaCategoryOther GetMetaCategory = "other" +) + +// +k8s:openapi-gen=true +type GetMetaState string + +const ( + GetMetaStateAlpha GetMetaState = "alpha" + GetMetaStateBeta GetMetaState = "beta" +) + +// +k8s:openapi-gen=true +type V0alpha1DependenciesPluginsType string + +const ( + V0alpha1DependenciesPluginsTypeApp V0alpha1DependenciesPluginsType = "app" + V0alpha1DependenciesPluginsTypeDatasource V0alpha1DependenciesPluginsType = "datasource" + V0alpha1DependenciesPluginsTypePanel V0alpha1DependenciesPluginsType = "panel" +) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_metadata_gen.go similarity index 85% rename from apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go rename to apps/plugins/pkg/apis/plugins/v0alpha1/plugin_metadata_gen.go index 7d3b3c9c6b8..fe30fe5e614 100644 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_metadata_gen.go @@ -9,7 +9,7 @@ import ( // metadata contains embedded CommonMetadata and can be extended with custom string fields // TODO: use CommonMetadata instead of redefining here; currently needs to be defined here // without external reference as using the CommonMetadata reference breaks thema codegen. -type PluginMetaMetadata struct { +type PluginMetadata struct { UpdateTimestamp time.Time `json:"updateTimestamp"` CreatedBy string `json:"createdBy"` Uid string `json:"uid"` @@ -22,9 +22,9 @@ type PluginMetaMetadata struct { Labels map[string]string `json:"labels"` } -// NewPluginMetaMetadata creates a new PluginMetaMetadata object. -func NewPluginMetaMetadata() *PluginMetaMetadata { - return &PluginMetaMetadata{ +// NewPluginMetadata creates a new PluginMetadata object. +func NewPluginMetadata() *PluginMetadata { + return &PluginMetadata{ Finalizers: []string{}, Labels: map[string]string{}, } diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_object_gen.go similarity index 69% rename from apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go rename to apps/plugins/pkg/apis/plugins/v0alpha1/plugin_object_gen.go index dac431ebf12..d2cdedba399 100644 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_object_gen.go @@ -15,22 +15,22 @@ import ( ) // +k8s:openapi-gen=true -type PluginMeta struct { +type Plugin struct { metav1.TypeMeta `json:",inline" yaml:",inline"` metav1.ObjectMeta `json:"metadata" yaml:"metadata"` - // Spec is the spec of the PluginMeta - Spec PluginMetaSpec `json:"spec" yaml:"spec"` + // Spec is the spec of the Plugin + Spec PluginSpec `json:"spec" yaml:"spec"` - Status PluginMetaStatus `json:"status" yaml:"status"` + Status PluginStatus `json:"status" yaml:"status"` } -func (o *PluginMeta) GetSpec() any { +func (o *Plugin) GetSpec() any { return o.Spec } -func (o *PluginMeta) SetSpec(spec any) error { - cast, ok := spec.(PluginMetaSpec) +func (o *Plugin) SetSpec(spec any) error { + cast, ok := spec.(PluginSpec) if !ok { return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) } @@ -38,13 +38,13 @@ func (o *PluginMeta) SetSpec(spec any) error { return nil } -func (o *PluginMeta) GetSubresources() map[string]any { +func (o *Plugin) GetSubresources() map[string]any { return map[string]any{ "status": o.Status, } } -func (o *PluginMeta) GetSubresource(name string) (any, bool) { +func (o *Plugin) GetSubresource(name string) (any, bool) { switch name { case "status": return o.Status, true @@ -53,12 +53,12 @@ func (o *PluginMeta) GetSubresource(name string) (any, bool) { } } -func (o *PluginMeta) SetSubresource(name string, value any) error { +func (o *Plugin) SetSubresource(name string, value any) error { switch name { case "status": - cast, ok := value.(PluginMetaStatus) + cast, ok := value.(PluginStatus) if !ok { - return fmt.Errorf("cannot set status type %#v, not of type PluginMetaStatus", value) + return fmt.Errorf("cannot set status type %#v, not of type PluginStatus", value) } o.Status = cast return nil @@ -67,7 +67,7 @@ func (o *PluginMeta) SetSubresource(name string, value any) error { } } -func (o *PluginMeta) GetStaticMetadata() resource.StaticMetadata { +func (o *Plugin) GetStaticMetadata() resource.StaticMetadata { gvk := o.GroupVersionKind() return resource.StaticMetadata{ Name: o.ObjectMeta.Name, @@ -78,7 +78,7 @@ func (o *PluginMeta) GetStaticMetadata() resource.StaticMetadata { } } -func (o *PluginMeta) SetStaticMetadata(metadata resource.StaticMetadata) { +func (o *Plugin) SetStaticMetadata(metadata resource.StaticMetadata) { o.Name = metadata.Name o.Namespace = metadata.Namespace o.SetGroupVersionKind(schema.GroupVersionKind{ @@ -88,7 +88,7 @@ func (o *PluginMeta) SetStaticMetadata(metadata resource.StaticMetadata) { }) } -func (o *PluginMeta) GetCommonMetadata() resource.CommonMetadata { +func (o *Plugin) GetCommonMetadata() resource.CommonMetadata { dt := o.DeletionTimestamp var deletionTimestamp *time.Time if dt != nil { @@ -120,7 +120,7 @@ func (o *PluginMeta) GetCommonMetadata() resource.CommonMetadata { } } -func (o *PluginMeta) SetCommonMetadata(metadata resource.CommonMetadata) { +func (o *Plugin) SetCommonMetadata(metadata resource.CommonMetadata) { o.UID = types.UID(metadata.UID) o.ResourceVersion = metadata.ResourceVersion o.Generation = metadata.Generation @@ -165,7 +165,7 @@ func (o *PluginMeta) SetCommonMetadata(metadata resource.CommonMetadata) { } } -func (o *PluginMeta) GetCreatedBy() string { +func (o *Plugin) GetCreatedBy() string { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -173,7 +173,7 @@ func (o *PluginMeta) GetCreatedBy() string { return o.ObjectMeta.Annotations["grafana.com/createdBy"] } -func (o *PluginMeta) SetCreatedBy(createdBy string) { +func (o *Plugin) SetCreatedBy(createdBy string) { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -181,7 +181,7 @@ func (o *PluginMeta) SetCreatedBy(createdBy string) { o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy } -func (o *PluginMeta) GetUpdateTimestamp() time.Time { +func (o *Plugin) GetUpdateTimestamp() time.Time { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -190,7 +190,7 @@ func (o *PluginMeta) GetUpdateTimestamp() time.Time { return parsed } -func (o *PluginMeta) SetUpdateTimestamp(updateTimestamp time.Time) { +func (o *Plugin) SetUpdateTimestamp(updateTimestamp time.Time) { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -198,7 +198,7 @@ func (o *PluginMeta) SetUpdateTimestamp(updateTimestamp time.Time) { o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339) } -func (o *PluginMeta) GetUpdatedBy() string { +func (o *Plugin) GetUpdatedBy() string { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -206,7 +206,7 @@ func (o *PluginMeta) GetUpdatedBy() string { return o.ObjectMeta.Annotations["grafana.com/updatedBy"] } -func (o *PluginMeta) SetUpdatedBy(updatedBy string) { +func (o *Plugin) SetUpdatedBy(updatedBy string) { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -214,21 +214,21 @@ func (o *PluginMeta) SetUpdatedBy(updatedBy string) { o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy } -func (o *PluginMeta) Copy() resource.Object { +func (o *Plugin) Copy() resource.Object { return resource.CopyObject(o) } -func (o *PluginMeta) DeepCopyObject() runtime.Object { +func (o *Plugin) DeepCopyObject() runtime.Object { return o.Copy() } -func (o *PluginMeta) DeepCopy() *PluginMeta { - cpy := &PluginMeta{} +func (o *Plugin) DeepCopy() *Plugin { + cpy := &Plugin{} o.DeepCopyInto(cpy) return cpy } -func (o *PluginMeta) DeepCopyInto(dst *PluginMeta) { +func (o *Plugin) DeepCopyInto(dst *Plugin) { dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion dst.TypeMeta.Kind = o.TypeMeta.Kind o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) @@ -237,34 +237,34 @@ func (o *PluginMeta) DeepCopyInto(dst *PluginMeta) { } // Interface compliance compile-time check -var _ resource.Object = &PluginMeta{} +var _ resource.Object = &Plugin{} // +k8s:openapi-gen=true -type PluginMetaList struct { +type PluginList struct { metav1.TypeMeta `json:",inline" yaml:",inline"` metav1.ListMeta `json:"metadata" yaml:"metadata"` - Items []PluginMeta `json:"items" yaml:"items"` + Items []Plugin `json:"items" yaml:"items"` } -func (o *PluginMetaList) DeepCopyObject() runtime.Object { +func (o *PluginList) DeepCopyObject() runtime.Object { return o.Copy() } -func (o *PluginMetaList) Copy() resource.ListObject { - cpy := &PluginMetaList{ +func (o *PluginList) Copy() resource.ListObject { + cpy := &PluginList{ TypeMeta: o.TypeMeta, - Items: make([]PluginMeta, len(o.Items)), + Items: make([]Plugin, len(o.Items)), } o.ListMeta.DeepCopyInto(&cpy.ListMeta) for i := 0; i < len(o.Items); i++ { - if item, ok := o.Items[i].Copy().(*PluginMeta); ok { + if item, ok := o.Items[i].Copy().(*Plugin); ok { cpy.Items[i] = *item } } return cpy } -func (o *PluginMetaList) GetItems() []resource.Object { +func (o *PluginList) GetItems() []resource.Object { items := make([]resource.Object, len(o.Items)) for i := 0; i < len(o.Items); i++ { items[i] = &o.Items[i] @@ -272,48 +272,48 @@ func (o *PluginMetaList) GetItems() []resource.Object { return items } -func (o *PluginMetaList) SetItems(items []resource.Object) { - o.Items = make([]PluginMeta, len(items)) +func (o *PluginList) SetItems(items []resource.Object) { + o.Items = make([]Plugin, len(items)) for i := 0; i < len(items); i++ { - o.Items[i] = *items[i].(*PluginMeta) + o.Items[i] = *items[i].(*Plugin) } } -func (o *PluginMetaList) DeepCopy() *PluginMetaList { - cpy := &PluginMetaList{} +func (o *PluginList) DeepCopy() *PluginList { + cpy := &PluginList{} o.DeepCopyInto(cpy) return cpy } -func (o *PluginMetaList) DeepCopyInto(dst *PluginMetaList) { +func (o *PluginList) DeepCopyInto(dst *PluginList) { resource.CopyObjectInto(dst, o) } // Interface compliance compile-time check -var _ resource.ListObject = &PluginMetaList{} +var _ resource.ListObject = &PluginList{} // Copy methods for all subresource types // DeepCopy creates a full deep copy of Spec -func (s *PluginMetaSpec) DeepCopy() *PluginMetaSpec { - cpy := &PluginMetaSpec{} +func (s *PluginSpec) DeepCopy() *PluginSpec { + cpy := &PluginSpec{} s.DeepCopyInto(cpy) return cpy } // DeepCopyInto deep copies Spec into another Spec object -func (s *PluginMetaSpec) DeepCopyInto(dst *PluginMetaSpec) { +func (s *PluginSpec) DeepCopyInto(dst *PluginSpec) { resource.CopyObjectInto(dst, s) } -// DeepCopy creates a full deep copy of PluginMetaStatus -func (s *PluginMetaStatus) DeepCopy() *PluginMetaStatus { - cpy := &PluginMetaStatus{} +// DeepCopy creates a full deep copy of PluginStatus +func (s *PluginStatus) DeepCopy() *PluginStatus { + cpy := &PluginStatus{} s.DeepCopyInto(cpy) return cpy } -// DeepCopyInto deep copies PluginMetaStatus into another PluginMetaStatus object -func (s *PluginMetaStatus) DeepCopyInto(dst *PluginMetaStatus) { +// DeepCopyInto deep copies PluginStatus into another PluginStatus object +func (s *PluginStatus) DeepCopyInto(dst *PluginStatus) { resource.CopyObjectInto(dst, s) } diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_schema_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_schema_gen.go new file mode 100644 index 00000000000..97024275a1c --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_schema_gen.go @@ -0,0 +1,34 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" +) + +// schema is unexported to prevent accidental overwrites +var ( + schemaPlugin = resource.NewSimpleSchema("plugins.grafana.app", "v0alpha1", &Plugin{}, &PluginList{}, resource.WithKind("Plugin"), + resource.WithPlural("plugins"), resource.WithScope(resource.NamespacedScope)) + kindPlugin = resource.Kind{ + Schema: schemaPlugin, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &PluginJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func PluginKind() resource.Kind { + return kindPlugin +} + +// Schema returns a resource.SimpleSchema representation of Plugin +func PluginSchema() *resource.SimpleSchema { + return schemaPlugin +} + +// Interface compliance checks +var _ resource.Schema = kindPlugin diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_spec_gen.go new file mode 100644 index 00000000000..d46a0a4aed1 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_spec_gen.go @@ -0,0 +1,25 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type PluginSpec struct { + Id string `json:"id"` + Version string `json:"version"` + Url *string `json:"url,omitempty"` + Class PluginSpecClass `json:"class"` +} + +// NewPluginSpec creates a new PluginSpec object. +func NewPluginSpec() *PluginSpec { + return &PluginSpec{} +} + +// +k8s:openapi-gen=true +type PluginSpecClass string + +const ( + PluginSpecClassCore PluginSpecClass = "core" + PluginSpecClassExternal PluginSpecClass = "external" + PluginSpecClassCdn PluginSpecClass = "cdn" +) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_status_gen.go similarity index 52% rename from apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go rename to apps/plugins/pkg/apis/plugins/v0alpha1/plugin_status_gen.go index 60fa37dbb32..55ecac4408f 100644 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_status_gen.go @@ -3,42 +3,42 @@ package v0alpha1 // +k8s:openapi-gen=true -type PluginMetastatusOperatorState struct { +type PluginstatusOperatorState struct { // lastEvaluation is the ResourceVersion last evaluated LastEvaluation string `json:"lastEvaluation"` // state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. - State PluginMetaStatusOperatorStateState `json:"state"` + State PluginStatusOperatorStateState `json:"state"` // descriptiveState is an optional more descriptive state field which has no requirements on format DescriptiveState *string `json:"descriptiveState,omitempty"` // details contains any extra information that is operator-specific Details map[string]interface{} `json:"details,omitempty"` } -// NewPluginMetastatusOperatorState creates a new PluginMetastatusOperatorState object. -func NewPluginMetastatusOperatorState() *PluginMetastatusOperatorState { - return &PluginMetastatusOperatorState{} +// NewPluginstatusOperatorState creates a new PluginstatusOperatorState object. +func NewPluginstatusOperatorState() *PluginstatusOperatorState { + return &PluginstatusOperatorState{} } // +k8s:openapi-gen=true -type PluginMetaStatus struct { +type PluginStatus struct { // operatorStates is a map of operator ID to operator state evaluations. // Any operator which consumes this kind SHOULD add its state evaluation information to this field. - OperatorStates map[string]PluginMetastatusOperatorState `json:"operatorStates,omitempty"` + OperatorStates map[string]PluginstatusOperatorState `json:"operatorStates,omitempty"` // additionalFields is reserved for future use AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` } -// NewPluginMetaStatus creates a new PluginMetaStatus object. -func NewPluginMetaStatus() *PluginMetaStatus { - return &PluginMetaStatus{} +// NewPluginStatus creates a new PluginStatus object. +func NewPluginStatus() *PluginStatus { + return &PluginStatus{} } // +k8s:openapi-gen=true -type PluginMetaStatusOperatorStateState string +type PluginStatusOperatorStateState string const ( - PluginMetaStatusOperatorStateStateSuccess PluginMetaStatusOperatorStateState = "success" - PluginMetaStatusOperatorStateStateInProgress PluginMetaStatusOperatorStateState = "in_progress" - PluginMetaStatusOperatorStateStateFailed PluginMetaStatusOperatorStateState = "failed" + PluginStatusOperatorStateStateSuccess PluginStatusOperatorStateState = "success" + PluginStatusOperatorStateStateInProgress PluginStatusOperatorStateState = "in_progress" + PluginStatusOperatorStateStateFailed PluginStatusOperatorStateState = "failed" ) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_client_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_client_gen.go deleted file mode 100644 index 7733db871e0..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_client_gen.go +++ /dev/null @@ -1,99 +0,0 @@ -package v0alpha1 - -import ( - "context" - - "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type PluginInstallClient struct { - client *resource.TypedClient[*PluginInstall, *PluginInstallList] -} - -func NewPluginInstallClient(client resource.Client) *PluginInstallClient { - return &PluginInstallClient{ - client: resource.NewTypedClient[*PluginInstall, *PluginInstallList](client, PluginInstallKind()), - } -} - -func NewPluginInstallClientFromGenerator(generator resource.ClientGenerator) (*PluginInstallClient, error) { - c, err := generator.ClientFor(PluginInstallKind()) - if err != nil { - return nil, err - } - return NewPluginInstallClient(c), nil -} - -func (c *PluginInstallClient) Get(ctx context.Context, identifier resource.Identifier) (*PluginInstall, error) { - return c.client.Get(ctx, identifier) -} - -func (c *PluginInstallClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginInstallList, error) { - return c.client.List(ctx, namespace, opts) -} - -func (c *PluginInstallClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginInstallList, error) { - resp, err := c.client.List(ctx, namespace, resource.ListOptions{ - ResourceVersion: opts.ResourceVersion, - Limit: opts.Limit, - LabelFilters: opts.LabelFilters, - FieldSelectors: opts.FieldSelectors, - }) - if err != nil { - return nil, err - } - for resp.GetContinue() != "" { - page, err := c.client.List(ctx, namespace, resource.ListOptions{ - Continue: resp.GetContinue(), - ResourceVersion: opts.ResourceVersion, - Limit: opts.Limit, - LabelFilters: opts.LabelFilters, - FieldSelectors: opts.FieldSelectors, - }) - if err != nil { - return nil, err - } - resp.SetContinue(page.GetContinue()) - resp.SetResourceVersion(page.GetResourceVersion()) - resp.SetItems(append(resp.GetItems(), page.GetItems()...)) - } - return resp, nil -} - -func (c *PluginInstallClient) Create(ctx context.Context, obj *PluginInstall, opts resource.CreateOptions) (*PluginInstall, error) { - // Make sure apiVersion and kind are set - obj.APIVersion = GroupVersion.Identifier() - obj.Kind = PluginInstallKind().Kind() - return c.client.Create(ctx, obj, opts) -} - -func (c *PluginInstallClient) Update(ctx context.Context, obj *PluginInstall, opts resource.UpdateOptions) (*PluginInstall, error) { - return c.client.Update(ctx, obj, opts) -} - -func (c *PluginInstallClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*PluginInstall, error) { - return c.client.Patch(ctx, identifier, req, opts) -} - -func (c *PluginInstallClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus PluginInstallStatus, opts resource.UpdateOptions) (*PluginInstall, error) { - return c.client.Update(ctx, &PluginInstall{ - TypeMeta: metav1.TypeMeta{ - Kind: PluginInstallKind().Kind(), - APIVersion: GroupVersion.Identifier(), - }, - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: opts.ResourceVersion, - Namespace: identifier.Namespace, - Name: identifier.Name, - }, - Status: newStatus, - }, resource.UpdateOptions{ - Subresource: "status", - ResourceVersion: opts.ResourceVersion, - }) -} - -func (c *PluginInstallClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { - return c.client.Delete(ctx, identifier, opts) -} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go deleted file mode 100644 index 48b7e98fd2f..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go +++ /dev/null @@ -1,28 +0,0 @@ -// -// Code generated by grafana-app-sdk. DO NOT EDIT. -// - -package v0alpha1 - -import ( - "encoding/json" - "io" - - "github.com/grafana/grafana-app-sdk/resource" -) - -// PluginInstallJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding -type PluginInstallJSONCodec struct{} - -// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` -func (*PluginInstallJSONCodec) Read(reader io.Reader, into resource.Object) error { - return json.NewDecoder(reader).Decode(into) -} - -// Write writes JSON-encoded bytes into `writer` marshaled from `from` -func (*PluginInstallJSONCodec) Write(writer io.Writer, from resource.Object) error { - return json.NewEncoder(writer).Encode(from) -} - -// Interface compliance checks -var _ resource.Codec = &PluginInstallJSONCodec{} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go deleted file mode 100644 index 0e944597301..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go +++ /dev/null @@ -1,31 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -import ( - time "time" -) - -// metadata contains embedded CommonMetadata and can be extended with custom string fields -// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here -// without external reference as using the CommonMetadata reference breaks thema codegen. -type PluginInstallMetadata struct { - UpdateTimestamp time.Time `json:"updateTimestamp"` - CreatedBy string `json:"createdBy"` - Uid string `json:"uid"` - CreationTimestamp time.Time `json:"creationTimestamp"` - DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` - Finalizers []string `json:"finalizers"` - ResourceVersion string `json:"resourceVersion"` - Generation int64 `json:"generation"` - UpdatedBy string `json:"updatedBy"` - Labels map[string]string `json:"labels"` -} - -// NewPluginInstallMetadata creates a new PluginInstallMetadata object. -func NewPluginInstallMetadata() *PluginInstallMetadata { - return &PluginInstallMetadata{ - Finalizers: []string{}, - Labels: map[string]string{}, - } -} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go deleted file mode 100644 index 6ceaa603baa..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go +++ /dev/null @@ -1,319 +0,0 @@ -// -// Code generated by grafana-app-sdk. DO NOT EDIT. -// - -package v0alpha1 - -import ( - "fmt" - "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "time" -) - -// +k8s:openapi-gen=true -type PluginInstall struct { - metav1.TypeMeta `json:",inline" yaml:",inline"` - metav1.ObjectMeta `json:"metadata" yaml:"metadata"` - - // Spec is the spec of the PluginInstall - Spec PluginInstallSpec `json:"spec" yaml:"spec"` - - Status PluginInstallStatus `json:"status" yaml:"status"` -} - -func (o *PluginInstall) GetSpec() any { - return o.Spec -} - -func (o *PluginInstall) SetSpec(spec any) error { - cast, ok := spec.(PluginInstallSpec) - if !ok { - return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) - } - o.Spec = cast - return nil -} - -func (o *PluginInstall) GetSubresources() map[string]any { - return map[string]any{ - "status": o.Status, - } -} - -func (o *PluginInstall) GetSubresource(name string) (any, bool) { - switch name { - case "status": - return o.Status, true - default: - return nil, false - } -} - -func (o *PluginInstall) SetSubresource(name string, value any) error { - switch name { - case "status": - cast, ok := value.(PluginInstallStatus) - if !ok { - return fmt.Errorf("cannot set status type %#v, not of type PluginInstallStatus", value) - } - o.Status = cast - return nil - default: - return fmt.Errorf("subresource '%s' does not exist", name) - } -} - -func (o *PluginInstall) GetStaticMetadata() resource.StaticMetadata { - gvk := o.GroupVersionKind() - return resource.StaticMetadata{ - Name: o.ObjectMeta.Name, - Namespace: o.ObjectMeta.Namespace, - Group: gvk.Group, - Version: gvk.Version, - Kind: gvk.Kind, - } -} - -func (o *PluginInstall) SetStaticMetadata(metadata resource.StaticMetadata) { - o.Name = metadata.Name - o.Namespace = metadata.Namespace - o.SetGroupVersionKind(schema.GroupVersionKind{ - Group: metadata.Group, - Version: metadata.Version, - Kind: metadata.Kind, - }) -} - -func (o *PluginInstall) GetCommonMetadata() resource.CommonMetadata { - dt := o.DeletionTimestamp - var deletionTimestamp *time.Time - if dt != nil { - deletionTimestamp = &dt.Time - } - // Legacy ExtraFields support - extraFields := make(map[string]any) - if o.Annotations != nil { - extraFields["annotations"] = o.Annotations - } - if o.ManagedFields != nil { - extraFields["managedFields"] = o.ManagedFields - } - if o.OwnerReferences != nil { - extraFields["ownerReferences"] = o.OwnerReferences - } - return resource.CommonMetadata{ - UID: string(o.UID), - ResourceVersion: o.ResourceVersion, - Generation: o.Generation, - Labels: o.Labels, - CreationTimestamp: o.CreationTimestamp.Time, - DeletionTimestamp: deletionTimestamp, - Finalizers: o.Finalizers, - UpdateTimestamp: o.GetUpdateTimestamp(), - CreatedBy: o.GetCreatedBy(), - UpdatedBy: o.GetUpdatedBy(), - ExtraFields: extraFields, - } -} - -func (o *PluginInstall) SetCommonMetadata(metadata resource.CommonMetadata) { - o.UID = types.UID(metadata.UID) - o.ResourceVersion = metadata.ResourceVersion - o.Generation = metadata.Generation - o.Labels = metadata.Labels - o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp) - if metadata.DeletionTimestamp != nil { - dt := metav1.NewTime(*metadata.DeletionTimestamp) - o.DeletionTimestamp = &dt - } else { - o.DeletionTimestamp = nil - } - o.Finalizers = metadata.Finalizers - if o.Annotations == nil { - o.Annotations = make(map[string]string) - } - if !metadata.UpdateTimestamp.IsZero() { - o.SetUpdateTimestamp(metadata.UpdateTimestamp) - } - if metadata.CreatedBy != "" { - o.SetCreatedBy(metadata.CreatedBy) - } - if metadata.UpdatedBy != "" { - o.SetUpdatedBy(metadata.UpdatedBy) - } - // Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields - if metadata.ExtraFields != nil { - if annotations, ok := metadata.ExtraFields["annotations"]; ok { - if cast, ok := annotations.(map[string]string); ok { - o.Annotations = cast - } - } - if managedFields, ok := metadata.ExtraFields["managedFields"]; ok { - if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok { - o.ManagedFields = cast - } - } - if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok { - if cast, ok := ownerReferences.([]metav1.OwnerReference); ok { - o.OwnerReferences = cast - } - } - } -} - -func (o *PluginInstall) GetCreatedBy() string { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - return o.ObjectMeta.Annotations["grafana.com/createdBy"] -} - -func (o *PluginInstall) SetCreatedBy(createdBy string) { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy -} - -func (o *PluginInstall) GetUpdateTimestamp() time.Time { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"]) - return parsed -} - -func (o *PluginInstall) SetUpdateTimestamp(updateTimestamp time.Time) { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339) -} - -func (o *PluginInstall) GetUpdatedBy() string { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - return o.ObjectMeta.Annotations["grafana.com/updatedBy"] -} - -func (o *PluginInstall) SetUpdatedBy(updatedBy string) { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy -} - -func (o *PluginInstall) Copy() resource.Object { - return resource.CopyObject(o) -} - -func (o *PluginInstall) DeepCopyObject() runtime.Object { - return o.Copy() -} - -func (o *PluginInstall) DeepCopy() *PluginInstall { - cpy := &PluginInstall{} - o.DeepCopyInto(cpy) - return cpy -} - -func (o *PluginInstall) DeepCopyInto(dst *PluginInstall) { - dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion - dst.TypeMeta.Kind = o.TypeMeta.Kind - o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) - o.Spec.DeepCopyInto(&dst.Spec) - o.Status.DeepCopyInto(&dst.Status) -} - -// Interface compliance compile-time check -var _ resource.Object = &PluginInstall{} - -// +k8s:openapi-gen=true -type PluginInstallList struct { - metav1.TypeMeta `json:",inline" yaml:",inline"` - metav1.ListMeta `json:"metadata" yaml:"metadata"` - Items []PluginInstall `json:"items" yaml:"items"` -} - -func (o *PluginInstallList) DeepCopyObject() runtime.Object { - return o.Copy() -} - -func (o *PluginInstallList) Copy() resource.ListObject { - cpy := &PluginInstallList{ - TypeMeta: o.TypeMeta, - Items: make([]PluginInstall, len(o.Items)), - } - o.ListMeta.DeepCopyInto(&cpy.ListMeta) - for i := 0; i < len(o.Items); i++ { - if item, ok := o.Items[i].Copy().(*PluginInstall); ok { - cpy.Items[i] = *item - } - } - return cpy -} - -func (o *PluginInstallList) GetItems() []resource.Object { - items := make([]resource.Object, len(o.Items)) - for i := 0; i < len(o.Items); i++ { - items[i] = &o.Items[i] - } - return items -} - -func (o *PluginInstallList) SetItems(items []resource.Object) { - o.Items = make([]PluginInstall, len(items)) - for i := 0; i < len(items); i++ { - o.Items[i] = *items[i].(*PluginInstall) - } -} - -func (o *PluginInstallList) DeepCopy() *PluginInstallList { - cpy := &PluginInstallList{} - o.DeepCopyInto(cpy) - return cpy -} - -func (o *PluginInstallList) DeepCopyInto(dst *PluginInstallList) { - resource.CopyObjectInto(dst, o) -} - -// Interface compliance compile-time check -var _ resource.ListObject = &PluginInstallList{} - -// Copy methods for all subresource types - -// DeepCopy creates a full deep copy of Spec -func (s *PluginInstallSpec) DeepCopy() *PluginInstallSpec { - cpy := &PluginInstallSpec{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies Spec into another Spec object -func (s *PluginInstallSpec) DeepCopyInto(dst *PluginInstallSpec) { - resource.CopyObjectInto(dst, s) -} - -// DeepCopy creates a full deep copy of PluginInstallStatus -func (s *PluginInstallStatus) DeepCopy() *PluginInstallStatus { - cpy := &PluginInstallStatus{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies PluginInstallStatus into another PluginInstallStatus object -func (s *PluginInstallStatus) DeepCopyInto(dst *PluginInstallStatus) { - resource.CopyObjectInto(dst, s) -} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go deleted file mode 100644 index 85ea40cd157..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go +++ /dev/null @@ -1,34 +0,0 @@ -// -// Code generated by grafana-app-sdk. DO NOT EDIT. -// - -package v0alpha1 - -import ( - "github.com/grafana/grafana-app-sdk/resource" -) - -// schema is unexported to prevent accidental overwrites -var ( - schemaPluginInstall = resource.NewSimpleSchema("plugins.grafana.app", "v0alpha1", &PluginInstall{}, &PluginInstallList{}, resource.WithKind("PluginInstall"), - resource.WithPlural("plugininstalls"), resource.WithScope(resource.NamespacedScope)) - kindPluginInstall = resource.Kind{ - Schema: schemaPluginInstall, - Codecs: map[resource.KindEncoding]resource.Codec{ - resource.KindEncodingJSON: &PluginInstallJSONCodec{}, - }, - } -) - -// Kind returns a resource.Kind for this Schema with a JSON codec -func PluginInstallKind() resource.Kind { - return kindPluginInstall -} - -// Schema returns a resource.SimpleSchema representation of PluginInstall -func PluginInstallSchema() *resource.SimpleSchema { - return schemaPluginInstall -} - -// Interface compliance checks -var _ resource.Schema = kindPluginInstall diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go deleted file mode 100644 index eeccd7f656d..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -// +k8s:openapi-gen=true -type PluginInstallSpec struct { - Id string `json:"id"` - Version string `json:"version"` - Url *string `json:"url,omitempty"` - Class PluginInstallSpecClass `json:"class"` -} - -// NewPluginInstallSpec creates a new PluginInstallSpec object. -func NewPluginInstallSpec() *PluginInstallSpec { - return &PluginInstallSpec{} -} - -// +k8s:openapi-gen=true -type PluginInstallSpecClass string - -const ( - PluginInstallSpecClassCore PluginInstallSpecClass = "core" - PluginInstallSpecClassExternal PluginInstallSpecClass = "external" - PluginInstallSpecClassCdn PluginInstallSpecClass = "cdn" -) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go deleted file mode 100644 index 77fc07860b0..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -// +k8s:openapi-gen=true -type PluginInstallstatusOperatorState struct { - // lastEvaluation is the ResourceVersion last evaluated - LastEvaluation string `json:"lastEvaluation"` - // state describes the state of the lastEvaluation. - // It is limited to three possible states for machine evaluation. - State PluginInstallStatusOperatorStateState `json:"state"` - // descriptiveState is an optional more descriptive state field which has no requirements on format - DescriptiveState *string `json:"descriptiveState,omitempty"` - // details contains any extra information that is operator-specific - Details map[string]interface{} `json:"details,omitempty"` -} - -// NewPluginInstallstatusOperatorState creates a new PluginInstallstatusOperatorState object. -func NewPluginInstallstatusOperatorState() *PluginInstallstatusOperatorState { - return &PluginInstallstatusOperatorState{} -} - -// +k8s:openapi-gen=true -type PluginInstallStatus struct { - // operatorStates is a map of operator ID to operator state evaluations. - // Any operator which consumes this kind SHOULD add its state evaluation information to this field. - OperatorStates map[string]PluginInstallstatusOperatorState `json:"operatorStates,omitempty"` - // additionalFields is reserved for future use - AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` -} - -// NewPluginInstallStatus creates a new PluginInstallStatus object. -func NewPluginInstallStatus() *PluginInstallStatus { - return &PluginInstallStatus{} -} - -// +k8s:openapi-gen=true -type PluginInstallStatusOperatorStateState string - -const ( - PluginInstallStatusOperatorStateStateSuccess PluginInstallStatusOperatorStateState = "success" - PluginInstallStatusOperatorStateStateInProgress PluginInstallStatusOperatorStateState = "in_progress" - PluginInstallStatusOperatorStateStateFailed PluginInstallStatusOperatorStateState = "failed" -) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_client_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_client_gen.go deleted file mode 100644 index e7788e27a33..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_client_gen.go +++ /dev/null @@ -1,99 +0,0 @@ -package v0alpha1 - -import ( - "context" - - "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type PluginMetaClient struct { - client *resource.TypedClient[*PluginMeta, *PluginMetaList] -} - -func NewPluginMetaClient(client resource.Client) *PluginMetaClient { - return &PluginMetaClient{ - client: resource.NewTypedClient[*PluginMeta, *PluginMetaList](client, PluginMetaKind()), - } -} - -func NewPluginMetaClientFromGenerator(generator resource.ClientGenerator) (*PluginMetaClient, error) { - c, err := generator.ClientFor(PluginMetaKind()) - if err != nil { - return nil, err - } - return NewPluginMetaClient(c), nil -} - -func (c *PluginMetaClient) Get(ctx context.Context, identifier resource.Identifier) (*PluginMeta, error) { - return c.client.Get(ctx, identifier) -} - -func (c *PluginMetaClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginMetaList, error) { - return c.client.List(ctx, namespace, opts) -} - -func (c *PluginMetaClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginMetaList, error) { - resp, err := c.client.List(ctx, namespace, resource.ListOptions{ - ResourceVersion: opts.ResourceVersion, - Limit: opts.Limit, - LabelFilters: opts.LabelFilters, - FieldSelectors: opts.FieldSelectors, - }) - if err != nil { - return nil, err - } - for resp.GetContinue() != "" { - page, err := c.client.List(ctx, namespace, resource.ListOptions{ - Continue: resp.GetContinue(), - ResourceVersion: opts.ResourceVersion, - Limit: opts.Limit, - LabelFilters: opts.LabelFilters, - FieldSelectors: opts.FieldSelectors, - }) - if err != nil { - return nil, err - } - resp.SetContinue(page.GetContinue()) - resp.SetResourceVersion(page.GetResourceVersion()) - resp.SetItems(append(resp.GetItems(), page.GetItems()...)) - } - return resp, nil -} - -func (c *PluginMetaClient) Create(ctx context.Context, obj *PluginMeta, opts resource.CreateOptions) (*PluginMeta, error) { - // Make sure apiVersion and kind are set - obj.APIVersion = GroupVersion.Identifier() - obj.Kind = PluginMetaKind().Kind() - return c.client.Create(ctx, obj, opts) -} - -func (c *PluginMetaClient) Update(ctx context.Context, obj *PluginMeta, opts resource.UpdateOptions) (*PluginMeta, error) { - return c.client.Update(ctx, obj, opts) -} - -func (c *PluginMetaClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*PluginMeta, error) { - return c.client.Patch(ctx, identifier, req, opts) -} - -func (c *PluginMetaClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus PluginMetaStatus, opts resource.UpdateOptions) (*PluginMeta, error) { - return c.client.Update(ctx, &PluginMeta{ - TypeMeta: metav1.TypeMeta{ - Kind: PluginMetaKind().Kind(), - APIVersion: GroupVersion.Identifier(), - }, - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: opts.ResourceVersion, - Namespace: identifier.Namespace, - Name: identifier.Name, - }, - Status: newStatus, - }, resource.UpdateOptions{ - Subresource: "status", - ResourceVersion: opts.ResourceVersion, - }) -} - -func (c *PluginMetaClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { - return c.client.Delete(ctx, identifier, opts) -} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go deleted file mode 100644 index a4022c8de97..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go +++ /dev/null @@ -1,34 +0,0 @@ -// -// Code generated by grafana-app-sdk. DO NOT EDIT. -// - -package v0alpha1 - -import ( - "github.com/grafana/grafana-app-sdk/resource" -) - -// schema is unexported to prevent accidental overwrites -var ( - schemaPluginMeta = resource.NewSimpleSchema("plugins.grafana.app", "v0alpha1", &PluginMeta{}, &PluginMetaList{}, resource.WithKind("PluginMeta"), - resource.WithPlural("pluginmetas"), resource.WithScope(resource.NamespacedScope)) - kindPluginMeta = resource.Kind{ - Schema: schemaPluginMeta, - Codecs: map[resource.KindEncoding]resource.Codec{ - resource.KindEncodingJSON: &PluginMetaJSONCodec{}, - }, - } -) - -// Kind returns a resource.Kind for this Schema with a JSON codec -func PluginMetaKind() resource.Kind { - return kindPluginMeta -} - -// Schema returns a resource.SimpleSchema representation of PluginMeta -func PluginMetaSchema() *resource.SimpleSchema { - return schemaPluginMeta -} - -// Interface compliance checks -var _ resource.Schema = kindPluginMeta diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go deleted file mode 100644 index 8020e34efda..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go +++ /dev/null @@ -1,477 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -import ( - time "time" -) - -// JSON configuration schema for Grafana plugins -// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json -// +k8s:openapi-gen=true -type PluginMetaJSONData struct { - // Unique name of the plugin - Id string `json:"id"` - // Plugin type - Type PluginMetaJSONDataType `json:"type"` - // Human-readable name of the plugin - Name string `json:"name"` - // Metadata for the plugin - Info PluginMetaInfo `json:"info"` - // Dependency information - Dependencies PluginMetaDependencies `json:"dependencies"` - // Optional fields - Alerting *bool `json:"alerting,omitempty"` - Annotations *bool `json:"annotations,omitempty"` - AutoEnabled *bool `json:"autoEnabled,omitempty"` - Backend *bool `json:"backend,omitempty"` - BuildMode *string `json:"buildMode,omitempty"` - BuiltIn *bool `json:"builtIn,omitempty"` - Category *PluginMetaJSONDataCategory `json:"category,omitempty"` - EnterpriseFeatures *PluginMetaEnterpriseFeatures `json:"enterpriseFeatures,omitempty"` - Executable *string `json:"executable,omitempty"` - HideFromList *bool `json:"hideFromList,omitempty"` - // +listType=atomic - Includes []PluginMetaInclude `json:"includes,omitempty"` - Logs *bool `json:"logs,omitempty"` - Metrics *bool `json:"metrics,omitempty"` - MultiValueFilterOperators *bool `json:"multiValueFilterOperators,omitempty"` - PascalName *string `json:"pascalName,omitempty"` - Preload *bool `json:"preload,omitempty"` - QueryOptions *PluginMetaQueryOptions `json:"queryOptions,omitempty"` - // +listType=atomic - Routes []PluginMetaRoute `json:"routes,omitempty"` - SkipDataQuery *bool `json:"skipDataQuery,omitempty"` - State *PluginMetaJSONDataState `json:"state,omitempty"` - Streaming *bool `json:"streaming,omitempty"` - Tracing *bool `json:"tracing,omitempty"` - Iam *PluginMetaIAM `json:"iam,omitempty"` - // +listType=atomic - Roles []PluginMetaRole `json:"roles,omitempty"` - Extensions *PluginMetaExtensions `json:"extensions,omitempty"` -} - -// NewPluginMetaJSONData creates a new PluginMetaJSONData object. -func NewPluginMetaJSONData() *PluginMetaJSONData { - return &PluginMetaJSONData{ - Info: *NewPluginMetaInfo(), - Dependencies: *NewPluginMetaDependencies(), - } -} - -// +k8s:openapi-gen=true -type PluginMetaInfo struct { - // Required fields - // +listType=set - Keywords []string `json:"keywords"` - Logos PluginMetaV0alpha1InfoLogos `json:"logos"` - Updated time.Time `json:"updated"` - Version string `json:"version"` - // Optional fields - Author *PluginMetaV0alpha1InfoAuthor `json:"author,omitempty"` - Description *string `json:"description,omitempty"` - // +listType=atomic - Links []PluginMetaV0alpha1InfoLinks `json:"links,omitempty"` - // +listType=atomic - Screenshots []PluginMetaV0alpha1InfoScreenshots `json:"screenshots,omitempty"` -} - -// NewPluginMetaInfo creates a new PluginMetaInfo object. -func NewPluginMetaInfo() *PluginMetaInfo { - return &PluginMetaInfo{ - Keywords: []string{}, - Logos: *NewPluginMetaV0alpha1InfoLogos(), - } -} - -// +k8s:openapi-gen=true -type PluginMetaDependencies struct { - // Required field - GrafanaDependency string `json:"grafanaDependency"` - // Optional fields - GrafanaVersion *string `json:"grafanaVersion,omitempty"` - // +listType=set - // +listMapKey=id - Plugins []PluginMetaV0alpha1DependenciesPlugins `json:"plugins,omitempty"` - Extensions *PluginMetaV0alpha1DependenciesExtensions `json:"extensions,omitempty"` -} - -// NewPluginMetaDependencies creates a new PluginMetaDependencies object. -func NewPluginMetaDependencies() *PluginMetaDependencies { - return &PluginMetaDependencies{} -} - -// +k8s:openapi-gen=true -type PluginMetaEnterpriseFeatures struct { - // Allow additional properties - HealthDiagnosticsErrors *bool `json:"healthDiagnosticsErrors,omitempty"` -} - -// NewPluginMetaEnterpriseFeatures creates a new PluginMetaEnterpriseFeatures object. -func NewPluginMetaEnterpriseFeatures() *PluginMetaEnterpriseFeatures { - return &PluginMetaEnterpriseFeatures{ - HealthDiagnosticsErrors: (func(input bool) *bool { return &input })(false), - } -} - -// +k8s:openapi-gen=true -type PluginMetaInclude struct { - Uid *string `json:"uid,omitempty"` - Type *PluginMetaIncludeType `json:"type,omitempty"` - Name *string `json:"name,omitempty"` - Component *string `json:"component,omitempty"` - Role *PluginMetaIncludeRole `json:"role,omitempty"` - Action *string `json:"action,omitempty"` - Path *string `json:"path,omitempty"` - AddToNav *bool `json:"addToNav,omitempty"` - DefaultNav *bool `json:"defaultNav,omitempty"` - Icon *string `json:"icon,omitempty"` -} - -// NewPluginMetaInclude creates a new PluginMetaInclude object. -func NewPluginMetaInclude() *PluginMetaInclude { - return &PluginMetaInclude{} -} - -// +k8s:openapi-gen=true -type PluginMetaQueryOptions struct { - MaxDataPoints *bool `json:"maxDataPoints,omitempty"` - MinInterval *bool `json:"minInterval,omitempty"` - CacheTimeout *bool `json:"cacheTimeout,omitempty"` -} - -// NewPluginMetaQueryOptions creates a new PluginMetaQueryOptions object. -func NewPluginMetaQueryOptions() *PluginMetaQueryOptions { - return &PluginMetaQueryOptions{} -} - -// +k8s:openapi-gen=true -type PluginMetaRoute struct { - Path *string `json:"path,omitempty"` - Method *string `json:"method,omitempty"` - Url *string `json:"url,omitempty"` - ReqSignedIn *bool `json:"reqSignedIn,omitempty"` - ReqRole *string `json:"reqRole,omitempty"` - ReqAction *string `json:"reqAction,omitempty"` - // +listType=atomic - Headers []string `json:"headers,omitempty"` - Body map[string]interface{} `json:"body,omitempty"` - TokenAuth *PluginMetaV0alpha1RouteTokenAuth `json:"tokenAuth,omitempty"` - JwtTokenAuth *PluginMetaV0alpha1RouteJwtTokenAuth `json:"jwtTokenAuth,omitempty"` - // +listType=atomic - UrlParams []PluginMetaV0alpha1RouteUrlParams `json:"urlParams,omitempty"` -} - -// NewPluginMetaRoute creates a new PluginMetaRoute object. -func NewPluginMetaRoute() *PluginMetaRoute { - return &PluginMetaRoute{} -} - -// +k8s:openapi-gen=true -type PluginMetaIAM struct { - // +listType=atomic - Permissions []PluginMetaV0alpha1IAMPermissions `json:"permissions,omitempty"` -} - -// NewPluginMetaIAM creates a new PluginMetaIAM object. -func NewPluginMetaIAM() *PluginMetaIAM { - return &PluginMetaIAM{} -} - -// +k8s:openapi-gen=true -type PluginMetaRole struct { - Role *PluginMetaV0alpha1RoleRole `json:"role,omitempty"` - // +listType=set - Grants []string `json:"grants,omitempty"` -} - -// NewPluginMetaRole creates a new PluginMetaRole object. -func NewPluginMetaRole() *PluginMetaRole { - return &PluginMetaRole{} -} - -// +k8s:openapi-gen=true -type PluginMetaExtensions struct { - // +listType=atomic - AddedComponents []PluginMetaV0alpha1ExtensionsAddedComponents `json:"addedComponents,omitempty"` - // +listType=atomic - AddedLinks []PluginMetaV0alpha1ExtensionsAddedLinks `json:"addedLinks,omitempty"` - // +listType=set - // +listMapKey=id - ExposedComponents []PluginMetaV0alpha1ExtensionsExposedComponents `json:"exposedComponents,omitempty"` - // +listType=set - // +listMapKey=id - ExtensionPoints []PluginMetaV0alpha1ExtensionsExtensionPoints `json:"extensionPoints,omitempty"` -} - -// NewPluginMetaExtensions creates a new PluginMetaExtensions object. -func NewPluginMetaExtensions() *PluginMetaExtensions { - return &PluginMetaExtensions{} -} - -// +k8s:openapi-gen=true -type PluginMetaSpec struct { - PluginJSON PluginMetaJSONData `json:"pluginJSON"` -} - -// NewPluginMetaSpec creates a new PluginMetaSpec object. -func NewPluginMetaSpec() *PluginMetaSpec { - return &PluginMetaSpec{ - PluginJSON: *NewPluginMetaJSONData(), - } -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1InfoLogos struct { - Small string `json:"small"` - Large string `json:"large"` -} - -// NewPluginMetaV0alpha1InfoLogos creates a new PluginMetaV0alpha1InfoLogos object. -func NewPluginMetaV0alpha1InfoLogos() *PluginMetaV0alpha1InfoLogos { - return &PluginMetaV0alpha1InfoLogos{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1InfoAuthor struct { - Name *string `json:"name,omitempty"` - Email *string `json:"email,omitempty"` - Url *string `json:"url,omitempty"` -} - -// NewPluginMetaV0alpha1InfoAuthor creates a new PluginMetaV0alpha1InfoAuthor object. -func NewPluginMetaV0alpha1InfoAuthor() *PluginMetaV0alpha1InfoAuthor { - return &PluginMetaV0alpha1InfoAuthor{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1InfoLinks struct { - Name *string `json:"name,omitempty"` - Url *string `json:"url,omitempty"` -} - -// NewPluginMetaV0alpha1InfoLinks creates a new PluginMetaV0alpha1InfoLinks object. -func NewPluginMetaV0alpha1InfoLinks() *PluginMetaV0alpha1InfoLinks { - return &PluginMetaV0alpha1InfoLinks{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1InfoScreenshots struct { - Name *string `json:"name,omitempty"` - Path *string `json:"path,omitempty"` -} - -// NewPluginMetaV0alpha1InfoScreenshots creates a new PluginMetaV0alpha1InfoScreenshots object. -func NewPluginMetaV0alpha1InfoScreenshots() *PluginMetaV0alpha1InfoScreenshots { - return &PluginMetaV0alpha1InfoScreenshots{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1DependenciesPlugins struct { - Id string `json:"id"` - Type PluginMetaV0alpha1DependenciesPluginsType `json:"type"` - Name string `json:"name"` -} - -// NewPluginMetaV0alpha1DependenciesPlugins creates a new PluginMetaV0alpha1DependenciesPlugins object. -func NewPluginMetaV0alpha1DependenciesPlugins() *PluginMetaV0alpha1DependenciesPlugins { - return &PluginMetaV0alpha1DependenciesPlugins{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1DependenciesExtensions struct { - // +listType=set - ExposedComponents []string `json:"exposedComponents,omitempty"` -} - -// NewPluginMetaV0alpha1DependenciesExtensions creates a new PluginMetaV0alpha1DependenciesExtensions object. -func NewPluginMetaV0alpha1DependenciesExtensions() *PluginMetaV0alpha1DependenciesExtensions { - return &PluginMetaV0alpha1DependenciesExtensions{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1RouteTokenAuth struct { - Url *string `json:"url,omitempty"` - // +listType=set - Scopes []string `json:"scopes,omitempty"` - Params map[string]interface{} `json:"params,omitempty"` -} - -// NewPluginMetaV0alpha1RouteTokenAuth creates a new PluginMetaV0alpha1RouteTokenAuth object. -func NewPluginMetaV0alpha1RouteTokenAuth() *PluginMetaV0alpha1RouteTokenAuth { - return &PluginMetaV0alpha1RouteTokenAuth{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1RouteJwtTokenAuth struct { - Url *string `json:"url,omitempty"` - // +listType=set - Scopes []string `json:"scopes,omitempty"` - Params map[string]interface{} `json:"params,omitempty"` -} - -// NewPluginMetaV0alpha1RouteJwtTokenAuth creates a new PluginMetaV0alpha1RouteJwtTokenAuth object. -func NewPluginMetaV0alpha1RouteJwtTokenAuth() *PluginMetaV0alpha1RouteJwtTokenAuth { - return &PluginMetaV0alpha1RouteJwtTokenAuth{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1RouteUrlParams struct { - Name *string `json:"name,omitempty"` - Content *string `json:"content,omitempty"` -} - -// NewPluginMetaV0alpha1RouteUrlParams creates a new PluginMetaV0alpha1RouteUrlParams object. -func NewPluginMetaV0alpha1RouteUrlParams() *PluginMetaV0alpha1RouteUrlParams { - return &PluginMetaV0alpha1RouteUrlParams{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1IAMPermissions struct { - Action *string `json:"action,omitempty"` - Scope *string `json:"scope,omitempty"` -} - -// NewPluginMetaV0alpha1IAMPermissions creates a new PluginMetaV0alpha1IAMPermissions object. -func NewPluginMetaV0alpha1IAMPermissions() *PluginMetaV0alpha1IAMPermissions { - return &PluginMetaV0alpha1IAMPermissions{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1RoleRolePermissions struct { - Action *string `json:"action,omitempty"` - Scope *string `json:"scope,omitempty"` -} - -// NewPluginMetaV0alpha1RoleRolePermissions creates a new PluginMetaV0alpha1RoleRolePermissions object. -func NewPluginMetaV0alpha1RoleRolePermissions() *PluginMetaV0alpha1RoleRolePermissions { - return &PluginMetaV0alpha1RoleRolePermissions{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1RoleRole struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - // +listType=atomic - Permissions []PluginMetaV0alpha1RoleRolePermissions `json:"permissions,omitempty"` -} - -// NewPluginMetaV0alpha1RoleRole creates a new PluginMetaV0alpha1RoleRole object. -func NewPluginMetaV0alpha1RoleRole() *PluginMetaV0alpha1RoleRole { - return &PluginMetaV0alpha1RoleRole{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1ExtensionsAddedComponents struct { - // +listType=set - Targets []string `json:"targets"` - Title string `json:"title"` - Description *string `json:"description,omitempty"` -} - -// NewPluginMetaV0alpha1ExtensionsAddedComponents creates a new PluginMetaV0alpha1ExtensionsAddedComponents object. -func NewPluginMetaV0alpha1ExtensionsAddedComponents() *PluginMetaV0alpha1ExtensionsAddedComponents { - return &PluginMetaV0alpha1ExtensionsAddedComponents{ - Targets: []string{}, - } -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1ExtensionsAddedLinks struct { - // +listType=set - Targets []string `json:"targets"` - Title string `json:"title"` - Description *string `json:"description,omitempty"` -} - -// NewPluginMetaV0alpha1ExtensionsAddedLinks creates a new PluginMetaV0alpha1ExtensionsAddedLinks object. -func NewPluginMetaV0alpha1ExtensionsAddedLinks() *PluginMetaV0alpha1ExtensionsAddedLinks { - return &PluginMetaV0alpha1ExtensionsAddedLinks{ - Targets: []string{}, - } -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1ExtensionsExposedComponents struct { - Id string `json:"id"` - Title *string `json:"title,omitempty"` - Description *string `json:"description,omitempty"` -} - -// NewPluginMetaV0alpha1ExtensionsExposedComponents creates a new PluginMetaV0alpha1ExtensionsExposedComponents object. -func NewPluginMetaV0alpha1ExtensionsExposedComponents() *PluginMetaV0alpha1ExtensionsExposedComponents { - return &PluginMetaV0alpha1ExtensionsExposedComponents{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1ExtensionsExtensionPoints struct { - Id string `json:"id"` - Title *string `json:"title,omitempty"` - Description *string `json:"description,omitempty"` -} - -// NewPluginMetaV0alpha1ExtensionsExtensionPoints creates a new PluginMetaV0alpha1ExtensionsExtensionPoints object. -func NewPluginMetaV0alpha1ExtensionsExtensionPoints() *PluginMetaV0alpha1ExtensionsExtensionPoints { - return &PluginMetaV0alpha1ExtensionsExtensionPoints{} -} - -// +k8s:openapi-gen=true -type PluginMetaJSONDataType string - -const ( - PluginMetaJSONDataTypeApp PluginMetaJSONDataType = "app" - PluginMetaJSONDataTypeDatasource PluginMetaJSONDataType = "datasource" - PluginMetaJSONDataTypePanel PluginMetaJSONDataType = "panel" - PluginMetaJSONDataTypeRenderer PluginMetaJSONDataType = "renderer" -) - -// +k8s:openapi-gen=true -type PluginMetaJSONDataCategory string - -const ( - PluginMetaJSONDataCategoryTsdb PluginMetaJSONDataCategory = "tsdb" - PluginMetaJSONDataCategoryLogging PluginMetaJSONDataCategory = "logging" - PluginMetaJSONDataCategoryCloud PluginMetaJSONDataCategory = "cloud" - PluginMetaJSONDataCategoryTracing PluginMetaJSONDataCategory = "tracing" - PluginMetaJSONDataCategoryProfiling PluginMetaJSONDataCategory = "profiling" - PluginMetaJSONDataCategorySql PluginMetaJSONDataCategory = "sql" - PluginMetaJSONDataCategoryEnterprise PluginMetaJSONDataCategory = "enterprise" - PluginMetaJSONDataCategoryIot PluginMetaJSONDataCategory = "iot" - PluginMetaJSONDataCategoryOther PluginMetaJSONDataCategory = "other" -) - -// +k8s:openapi-gen=true -type PluginMetaJSONDataState string - -const ( - PluginMetaJSONDataStateAlpha PluginMetaJSONDataState = "alpha" - PluginMetaJSONDataStateBeta PluginMetaJSONDataState = "beta" -) - -// +k8s:openapi-gen=true -type PluginMetaIncludeType string - -const ( - PluginMetaIncludeTypeDashboard PluginMetaIncludeType = "dashboard" - PluginMetaIncludeTypePage PluginMetaIncludeType = "page" - PluginMetaIncludeTypePanel PluginMetaIncludeType = "panel" - PluginMetaIncludeTypeDatasource PluginMetaIncludeType = "datasource" -) - -// +k8s:openapi-gen=true -type PluginMetaIncludeRole string - -const ( - PluginMetaIncludeRoleAdmin PluginMetaIncludeRole = "Admin" - PluginMetaIncludeRoleEditor PluginMetaIncludeRole = "Editor" - PluginMetaIncludeRoleViewer PluginMetaIncludeRole = "Viewer" -) - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1DependenciesPluginsType string - -const ( - PluginMetaV0alpha1DependenciesPluginsTypeApp PluginMetaV0alpha1DependenciesPluginsType = "app" - PluginMetaV0alpha1DependenciesPluginsTypeDatasource PluginMetaV0alpha1DependenciesPluginsType = "datasource" - PluginMetaV0alpha1DependenciesPluginsTypePanel PluginMetaV0alpha1DependenciesPluginsType = "panel" -) diff --git a/apps/plugins/pkg/apis/plugins_manifest.go b/apps/plugins/pkg/apis/plugins_manifest.go index 84a55555c2b..428971aa0b5 100644 --- a/apps/plugins/pkg/apis/plugins_manifest.go +++ b/apps/plugins/pkg/apis/plugins_manifest.go @@ -20,12 +20,9 @@ import ( ) var ( - rawSchemaPluginMetav0alpha1 = []byte(`{"Dependencies":{"additionalProperties":false,"properties":{"extensions":{"additionalProperties":false,"properties":{"exposedComponents":{"description":"+listType=set","items":{"type":"string"},"type":"array"}},"type":"object"},"grafanaDependency":{"description":"Required field","type":"string"},"grafanaVersion":{"description":"Optional fields","type":"string"},"plugins":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"enum":["app","datasource","panel"],"type":"string"}},"required":["id","type","name"],"type":"object"},"type":"array"}},"required":["grafanaDependency"],"type":"object"},"EnterpriseFeatures":{"additionalProperties":false,"properties":{"healthDiagnosticsErrors":{"default":false,"description":"Allow additional properties","type":"boolean"}},"type":"object"},"Extensions":{"additionalProperties":false,"properties":{"addedComponents":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"addedLinks":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"exposedComponents":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"},"extensionPoints":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"}},"type":"object"},"IAM":{"additionalProperties":false,"properties":{"permissions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"Include":{"additionalProperties":false,"properties":{"action":{"type":"string"},"addToNav":{"type":"boolean"},"component":{"type":"string"},"defaultNav":{"type":"boolean"},"icon":{"type":"string"},"name":{"type":"string"},"path":{"type":"string"},"role":{"enum":["Admin","Editor","Viewer"],"type":"string"},"type":{"enum":["dashboard","page","panel","datasource"],"type":"string"},"uid":{"type":"string"}},"type":"object"},"Info":{"additionalProperties":false,"properties":{"author":{"additionalProperties":false,"description":"Optional fields","properties":{"email":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"description":{"type":"string"},"keywords":{"description":"Required fields\n+listType=set","items":{"type":"string"},"type":"array"},"links":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"type":"array"},"logos":{"additionalProperties":false,"properties":{"large":{"type":"string"},"small":{"type":"string"}},"required":["small","large"],"type":"object"},"screenshots":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"name":{"type":"string"},"path":{"type":"string"}},"type":"object"},"type":"array"},"updated":{"format":"date-time","type":"string"},"version":{"type":"string"}},"required":["keywords","logos","updated","version"],"type":"object"},"JSONData":{"additionalProperties":false,"description":"JSON configuration schema for Grafana plugins\nConverted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json","properties":{"alerting":{"description":"Optional fields","type":"boolean"},"annotations":{"type":"boolean"},"autoEnabled":{"type":"boolean"},"backend":{"type":"boolean"},"buildMode":{"type":"string"},"builtIn":{"type":"boolean"},"category":{"enum":["tsdb","logging","cloud","tracing","profiling","sql","enterprise","iot","other"],"type":"string"},"dependencies":{"$ref":"#/components/schemas/Dependencies","description":"Dependency information"},"enterpriseFeatures":{"$ref":"#/components/schemas/EnterpriseFeatures"},"executable":{"type":"string"},"extensions":{"$ref":"#/components/schemas/Extensions"},"hideFromList":{"type":"boolean"},"iam":{"$ref":"#/components/schemas/IAM"},"id":{"description":"Unique name of the plugin","type":"string"},"includes":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Include"},"type":"array"},"info":{"$ref":"#/components/schemas/Info","description":"Metadata for the plugin"},"logs":{"type":"boolean"},"metrics":{"type":"boolean"},"multiValueFilterOperators":{"type":"boolean"},"name":{"description":"Human-readable name of the plugin","type":"string"},"pascalName":{"type":"string"},"preload":{"type":"boolean"},"queryOptions":{"$ref":"#/components/schemas/QueryOptions"},"roles":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Role"},"type":"array"},"routes":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Route"},"type":"array"},"skipDataQuery":{"type":"boolean"},"state":{"enum":["alpha","beta"],"type":"string"},"streaming":{"type":"boolean"},"tracing":{"type":"boolean"},"type":{"description":"Plugin type","enum":["app","datasource","panel","renderer"],"type":"string"}},"required":["id","type","name","info","dependencies"],"type":"object"},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"PluginMeta":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"QueryOptions":{"additionalProperties":false,"properties":{"cacheTimeout":{"type":"boolean"},"maxDataPoints":{"type":"boolean"},"minInterval":{"type":"boolean"}},"type":"object"},"Role":{"additionalProperties":false,"properties":{"grants":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"role":{"additionalProperties":false,"properties":{"description":{"type":"string"},"name":{"type":"string"},"permissions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"},"Route":{"additionalProperties":false,"properties":{"body":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"headers":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"jwtTokenAuth":{"additionalProperties":false,"properties":{"params":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"method":{"type":"string"},"path":{"type":"string"},"reqAction":{"type":"string"},"reqRole":{"type":"string"},"reqSignedIn":{"type":"boolean"},"tokenAuth":{"additionalProperties":false,"properties":{"params":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"url":{"type":"string"},"urlParams":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"content":{"type":"string"},"name":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"spec":{"additionalProperties":false,"properties":{"pluginJSON":{"$ref":"#/components/schemas/JSONData"}},"required":["pluginJSON"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) - versionSchemaPluginMetav0alpha1 app.VersionSchema - _ = json.Unmarshal(rawSchemaPluginMetav0alpha1, &versionSchemaPluginMetav0alpha1) - rawSchemaPluginInstallv0alpha1 = []byte(`{"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"PluginInstall":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"class":{"enum":["core","external","cdn"],"type":"string"},"id":{"type":"string"},"url":{"type":"string"},"version":{"type":"string"}},"required":["id","version","class"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) - versionSchemaPluginInstallv0alpha1 app.VersionSchema - _ = json.Unmarshal(rawSchemaPluginInstallv0alpha1, &versionSchemaPluginInstallv0alpha1) + rawSchemaPluginv0alpha1 = []byte(`{"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"Plugin":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"getMetaDependencies":{"type":"object","required":["grafanaDependency"],"properties":{"extensions":{"type":"object","properties":{"exposedComponents":{"description":"+listType=set","type":"array","items":{"type":"string"}}},"additionalProperties":false},"grafanaDependency":{"description":"Required field","type":"string"},"grafanaVersion":{"description":"Optional fields","type":"string"},"plugins":{"description":"+listType=set\n+listMapKey=id","type":"array","items":{"type":"object","required":["id","type","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["app","datasource","panel"]}},"additionalProperties":false}}},"additionalProperties":false},"getMetaEnterpriseFeatures":{"type":"object","properties":{"healthDiagnosticsErrors":{"description":"Allow additional properties","type":"boolean","default":false}},"additionalProperties":false},"getMetaExtensions":{"type":"object","properties":{"addedComponents":{"description":"+listType=atomic","type":"array","items":{"type":"object","required":["targets","title"],"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","type":"array","items":{"type":"string"}},"title":{"type":"string"}},"additionalProperties":false}},"addedLinks":{"description":"+listType=atomic","type":"array","items":{"type":"object","required":["targets","title"],"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","type":"array","items":{"type":"string"}},"title":{"type":"string"}},"additionalProperties":false}},"exposedComponents":{"description":"+listType=set\n+listMapKey=id","type":"array","items":{"type":"object","required":["id"],"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"additionalProperties":false}},"extensionPoints":{"description":"+listType=set\n+listMapKey=id","type":"array","items":{"type":"object","required":["id"],"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false},"getMetaIAM":{"type":"object","properties":{"permissions":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"scope":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false},"getMetaInclude":{"type":"object","properties":{"action":{"type":"string"},"addToNav":{"type":"boolean"},"component":{"type":"string"},"defaultNav":{"type":"boolean"},"icon":{"type":"string"},"name":{"type":"string"},"path":{"type":"string"},"role":{"type":"string","enum":["Admin","Editor","Viewer"]},"type":{"type":"string","enum":["dashboard","page","panel","datasource"]},"uid":{"type":"string"}},"additionalProperties":false},"getMetaInfo":{"type":"object","required":["keywords","logos","updated","version"],"properties":{"author":{"description":"Optional fields","type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"additionalProperties":false},"description":{"type":"string"},"keywords":{"description":"Required fields\n+listType=set","type":"array","items":{"type":"string"}},"links":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string"}},"additionalProperties":false}},"logos":{"type":"object","required":["small","large"],"properties":{"large":{"type":"string"},"small":{"type":"string"}},"additionalProperties":false},"screenshots":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"path":{"type":"string"}},"additionalProperties":false}},"updated":{"type":"string","format":"date-time"},"version":{"type":"string"}},"additionalProperties":false},"getMetaQueryOptions":{"type":"object","properties":{"cacheTimeout":{"type":"boolean"},"maxDataPoints":{"type":"boolean"},"minInterval":{"type":"boolean"}},"additionalProperties":false},"getMetaRole":{"type":"object","properties":{"grants":{"description":"+listType=set","type":"array","items":{"type":"string"}},"role":{"type":"object","properties":{"description":{"type":"string"},"name":{"type":"string"},"permissions":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"scope":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false}},"additionalProperties":false},"getMetaRoute":{"type":"object","properties":{"body":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}}},"headers":{"description":"+listType=atomic","type":"array","items":{"type":"string"}},"jwtTokenAuth":{"type":"object","properties":{"params":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}}},"scopes":{"description":"+listType=set","type":"array","items":{"type":"string"}},"url":{"type":"string"}},"additionalProperties":false},"method":{"type":"string"},"path":{"type":"string"},"reqAction":{"type":"string"},"reqRole":{"type":"string"},"reqSignedIn":{"type":"boolean"},"tokenAuth":{"type":"object","properties":{"params":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}}},"scopes":{"description":"+listType=set","type":"array","items":{"type":"string"}},"url":{"type":"string"}},"additionalProperties":false},"url":{"type":"string"},"urlParams":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"content":{"type":"string"},"name":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false},"spec":{"additionalProperties":false,"properties":{"class":{"enum":["core","external","cdn"],"type":"string"},"id":{"type":"string"},"url":{"type":"string"},"version":{"type":"string"}},"required":["id","version","class"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + versionSchemaPluginv0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaPluginv0alpha1, &versionSchemaPluginv0alpha1) ) var appManifestData = app.ManifestData{ @@ -35,22 +32,237 @@ var appManifestData = app.ManifestData{ Versions: []app.ManifestVersion{ { Name: "v0alpha1", - Served: false, + Served: true, Kinds: []app.ManifestVersionKind{ { - Kind: "PluginMeta", - Plural: "PluginMetas", + Kind: "Plugin", + Plural: "Plugins", Scope: "Namespaced", Conversion: false, - Schema: &versionSchemaPluginMetav0alpha1, - }, + Schema: &versionSchemaPluginv0alpha1, + Routes: map[string]spec3.PathProps{ + "/meta": { + Get: &spec3.Operation{ + OperationProps: spec3.OperationProps{ - { - Kind: "PluginInstall", - Plural: "PluginInstalls", - Scope: "Namespaced", - Conversion: false, - Schema: &versionSchemaPluginInstallv0alpha1, + OperationId: "getMeta", + + Responses: &spec3.Responses{ + ResponsesProps: spec3.ResponsesProps{ + Default: &spec3.Response{ + ResponseProps: spec3.ResponseProps{ + Description: "Default OK response", + Content: map[string]*spec3.MediaType{ + "application/json": { + MediaTypeProps: spec3.MediaTypeProps{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "alerting": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Description: "Optional fields", + }, + }, + "annotations": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "autoEnabled": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "backend": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "buildMode": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "builtIn": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "category": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Enum: []interface{}{ + "tsdb", + "logging", + "cloud", + "tracing", + "profiling", + "sql", + "enterprise", + "iot", + "other", + }, + }, + }, + "dependencies": { + SchemaProps: spec.SchemaProps{ + + Description: "Dependency information", + Ref: spec.MustCreateRef("#/components/schemas/getMetaDependencies"), + }, + }, + "enterpriseFeatures": { + SchemaProps: spec.SchemaProps{ + + Ref: spec.MustCreateRef("#/components/schemas/getMetaEnterpriseFeatures"), + }, + }, + "executable": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "extensions": { + SchemaProps: spec.SchemaProps{ + + Ref: spec.MustCreateRef("#/components/schemas/getMetaExtensions"), + }, + }, + "hideFromList": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "iam": { + SchemaProps: spec.SchemaProps{ + + Ref: spec.MustCreateRef("#/components/schemas/getMetaIAM"), + }, + }, + "id": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "Unique name of the plugin", + }, + }, + "includes": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Description: "+listType=atomic", + }, + }, + "info": { + SchemaProps: spec.SchemaProps{ + + Description: "Metadata for the plugin", + Ref: spec.MustCreateRef("#/components/schemas/getMetaInfo"), + }, + }, + "logs": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "metrics": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "multiValueFilterOperators": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "Human-readable name of the plugin", + }, + }, + "pascalName": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "preload": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "queryOptions": { + SchemaProps: spec.SchemaProps{ + + Ref: spec.MustCreateRef("#/components/schemas/getMetaQueryOptions"), + }, + }, + "roles": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Description: "+listType=atomic", + }, + }, + "routes": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Description: "+listType=atomic", + }, + }, + "skipDataQuery": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "state": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Enum: []interface{}{ + "alpha", + "beta", + }, + }, + }, + "streaming": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "tracing": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "Plugin type", + Enum: []interface{}{ + "app", + "datasource", + "panel", + "renderer", + }, + }, + }, + }, + Required: []string{ + "id", + "type", + "name", + "info", + "dependencies", + }, + }}, + }}, + }, + }, + }, + }}, + }, + }, + }, + }, }, }, Routes: app.ManifestVersionRoutes{ @@ -71,8 +283,7 @@ func RemoteManifest() app.Manifest { } var kindVersionToGoType = map[string]resource.Kind{ - "PluginMeta/v0alpha1": v0alpha1.PluginMetaKind(), - "PluginInstall/v0alpha1": v0alpha1.PluginInstallKind(), + "Plugin/v0alpha1": v0alpha1.PluginKind(), } // ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. @@ -82,7 +293,9 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist return goType, exists } -var customRouteToGoResponseType = map[string]any{} +var customRouteToGoResponseType = map[string]any{ + "v0alpha1|Plugin|meta|GET": v0alpha1.GetMeta{}, +} // ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. // kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. diff --git a/apps/plugins/pkg/app/app.go b/apps/plugins/pkg/app/app.go index 5503126704b..903e2580ab0 100644 --- a/apps/plugins/pkg/app/app.go +++ b/apps/plugins/pkg/app/app.go @@ -2,8 +2,11 @@ package app import ( "context" + "net/http" "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/k8s" + "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana-app-sdk/operator" "github.com/grafana/grafana-app-sdk/resource" "github.com/grafana/grafana-app-sdk/simple" @@ -11,16 +14,15 @@ import ( "k8s.io/klog/v2" pluginsapi "github.com/grafana/grafana/apps/plugins/pkg/apis" + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" ) func New(cfg app.Config) (app.App, error) { - managedKinds := []simple.AppManagedKind{} - for _, kinds := range GetKinds() { - for _, k := range kinds { - managedKinds = append(managedKinds, simple.AppManagedKind{ - Kind: k, - }) - } + cfg.KubeConfig.APIPath = "apis" + clientGenerator := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()) + client, err := pluginsv0alpha1.NewPluginClientFromGenerator(clientGenerator) + if err != nil { + return nil, err } simpleConfig := simple.AppConfig{ @@ -33,7 +35,32 @@ func New(cfg app.Config) (app.App, error) { }, }, }, - ManagedKinds: managedKinds, + ManagedKinds: []simple.AppManagedKind{ + { + Kind: pluginsv0alpha1.PluginKind(), + CustomRoutes: simple.AppCustomRouteHandlers{ + simple.AppCustomRoute{ + Method: http.MethodGet, + Path: "meta", + }: func(ctx context.Context, w app.CustomRouteResponseWriter, req *app.CustomRouteRequest) error { + plugin, err := client.Get(ctx, resource.Identifier{ + Namespace: req.ResourceIdentifier.Namespace, + Name: req.ResourceIdentifier.Name, + }) + if err != nil { + return err + } + logging.DefaultLogger.Debug("fetched plugin", "plugin", plugin) + // TODO: Implement this in future PR + w.WriteHeader(http.StatusNotImplemented) + if _, err := w.Write([]byte("Not implemented")); err != nil { + return err + } + return nil + }, + }, + }, + }, } a, err := simple.NewApp(simpleConfig) diff --git a/apps/plugins/pkg/app/install/registrar.go b/apps/plugins/pkg/app/install/registrar.go index efb7de11080..98d7c02b94b 100644 --- a/apps/plugins/pkg/app/install/registrar.go +++ b/apps/plugins/pkg/app/install/registrar.go @@ -41,12 +41,12 @@ type PluginInstall struct { Source Source } -func (p *PluginInstall) ToPluginInstallV0Alpha1(namespace string) *pluginsv0alpha1.PluginInstall { +func (p *PluginInstall) ToPluginInstallV0Alpha1(namespace string) *pluginsv0alpha1.Plugin { var url *string = nil if p.URL != "" { url = &p.URL } - return &pluginsv0alpha1.PluginInstall{ + return &pluginsv0alpha1.Plugin{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: p.ID, @@ -54,16 +54,16 @@ func (p *PluginInstall) ToPluginInstallV0Alpha1(namespace string) *pluginsv0alph PluginInstallSourceAnnotation: p.Source, }, }, - Spec: pluginsv0alpha1.PluginInstallSpec{ + Spec: pluginsv0alpha1.PluginSpec{ Id: p.ID, Version: p.Version, Url: url, - Class: pluginsv0alpha1.PluginInstallSpecClass(p.Class), + Class: pluginsv0alpha1.PluginSpecClass(p.Class), }, } } -func (p *PluginInstall) ShouldUpdate(existing *pluginsv0alpha1.PluginInstall) bool { +func (p *PluginInstall) ShouldUpdate(existing *pluginsv0alpha1.Plugin) bool { update := p.ToPluginInstallV0Alpha1(existing.Namespace) if source, ok := existing.Annotations[PluginInstallSourceAnnotation]; ok && source != p.Source { return true @@ -92,7 +92,7 @@ func equalStringPointers(a, b *string) bool { type InstallRegistrar struct { clientGenerator resource.ClientGenerator - client *pluginsv0alpha1.PluginInstallClient + client *pluginsv0alpha1.PluginClient clientOnce sync.Once } @@ -103,9 +103,9 @@ func NewInstallRegistrar(clientGenerator resource.ClientGenerator) *InstallRegis } } -func (r *InstallRegistrar) GetClient() (*pluginsv0alpha1.PluginInstallClient, error) { +func (r *InstallRegistrar) GetClient() (*pluginsv0alpha1.PluginClient, error) { r.clientOnce.Do(func() { - client, err := pluginsv0alpha1.NewPluginInstallClientFromGenerator(r.clientGenerator) + client, err := pluginsv0alpha1.NewPluginClientFromGenerator(r.clientGenerator) if err != nil { r.client = nil return diff --git a/apps/plugins/pkg/app/storage.go b/apps/plugins/pkg/app/storage.go deleted file mode 100644 index 9fca277abfc..00000000000 --- a/apps/plugins/pkg/app/storage.go +++ /dev/null @@ -1,75 +0,0 @@ -package app - -import ( - "context" - "strings" - - 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" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/registry/rest" - - claims "github.com/grafana/authlib/types" - pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" -) - -var ( - _ rest.Scoper = (*PluginMetaStorage)(nil) - _ rest.SingularNameProvider = (*PluginMetaStorage)(nil) - _ rest.Getter = (*PluginMetaStorage)(nil) - _ rest.Lister = (*PluginMetaStorage)(nil) - _ rest.Storage = (*PluginMetaStorage)(nil) - _ rest.TableConvertor = (*PluginMetaStorage)(nil) -) - -type PluginMetaStorage struct { - gr schema.GroupResource - namespacer claims.NamespaceFormatter - tableConverter rest.TableConvertor -} - -func NewPluginMetaStorage( - namespacer claims.NamespaceFormatter, -) *PluginMetaStorage { - gr := schema.GroupResource{ - Group: pluginsv0alpha1.PluginMetaKind().Group(), - Resource: strings.ToLower(pluginsv0alpha1.PluginMetaKind().Plural()), - } - return &PluginMetaStorage{ - gr: gr, - namespacer: namespacer, - tableConverter: rest.NewDefaultTableConvertor(gr), - } -} - -func (s *PluginMetaStorage) New() runtime.Object { - return pluginsv0alpha1.PluginMetaKind().ZeroValue() -} - -func (s *PluginMetaStorage) Destroy() {} - -func (s *PluginMetaStorage) NamespaceScoped() bool { - return true -} - -func (s *PluginMetaStorage) GetSingularName() string { - return strings.ToLower(pluginsv0alpha1.PluginMetaKind().Kind()) -} - -func (s *PluginMetaStorage) NewList() runtime.Object { - return pluginsv0alpha1.PluginMetaKind().ZeroListValue() -} - -func (s *PluginMetaStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) -} - -func (s *PluginMetaStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - return s.NewList(), nil -} - -func (s *PluginMetaStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - return nil, apierrors.NewNotFound(s.gr, name) -} diff --git a/pkg/registry/apps/plugins/register.go b/pkg/registry/apps/plugins/register.go index 2929efda78f..e95b120cd66 100644 --- a/pkg/registry/apps/plugins/register.go +++ b/pkg/registry/apps/plugins/register.go @@ -1,20 +1,15 @@ package plugins import ( - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/apiserver/pkg/registry/generic" - "k8s.io/apiserver/pkg/registry/rest" restclient "k8s.io/client-go/rest" "github.com/grafana/grafana-app-sdk/app" appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" "github.com/grafana/grafana-app-sdk/simple" pluginsappapis "github.com/grafana/grafana/apps/plugins/pkg/apis" - pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" pluginsapp "github.com/grafana/grafana/apps/plugins/pkg/app" "github.com/grafana/grafana/pkg/services/apiserver/appinstaller" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" ) @@ -26,16 +21,13 @@ var ( type PluginsAppInstaller struct { appsdkapiserver.AppInstaller - cfg *setting.Cfg } func RegisterAppInstaller( cfg *setting.Cfg, features featuremgmt.FeatureToggles, ) (*PluginsAppInstaller, error) { - installer := &PluginsAppInstaller{ - cfg: cfg, - } + installer := &PluginsAppInstaller{} specificConfig := any(nil) provider := simple.NewAppProvider(pluginsappapis.LocalManifest(), specificConfig, pluginsapp.New) appConfig := app.Config{ @@ -51,21 +43,6 @@ func RegisterAppInstaller( return installer, nil } -func (p *PluginsAppInstaller) InstallAPIs( - server appsdkapiserver.GenericAPIServer, - restOptsGetter generic.RESTOptionsGetter, -) error { - pluginMetaGVR := pluginsv0alpha1.PluginMetaKind().GroupVersionResource() - replacedStorage := map[schema.GroupVersionResource]rest.Storage{ - pluginMetaGVR: pluginsapp.NewPluginMetaStorage(request.GetNamespaceMapper(p.cfg)), - } - wrappedServer := &customStorageWrapper{ - wrapped: server, - replace: replacedStorage, - } - return p.AppInstaller.InstallAPIs(wrappedServer, restOptsGetter) -} - // GetAuthorizer returns the authorizer for the plugins app. func (p *PluginsAppInstaller) GetAuthorizer() authorizer.Authorizer { return pluginsapp.GetAuthorizer() diff --git a/pkg/services/pluginsintegration/installsync/syncer.go b/pkg/services/pluginsintegration/installsync/syncer.go index ac6d6ba3d32..1f056a6e85b 100644 --- a/pkg/services/pluginsintegration/installsync/syncer.go +++ b/pkg/services/pluginsintegration/installsync/syncer.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana-app-sdk/resource" "github.com/grafana/grafana/apps/plugins/pkg/app/install" @@ -90,6 +91,11 @@ func (s *syncer) Sync(ctx context.Context, source install.Source, installedPlugi return nil } + if !s.featureToggles.IsEnabled(ctx, featuremgmt.FlagPluginStoreServiceLoading) { + logging.DefaultLogger.Warn("pluginInstallAPISync is enabled, but pluginStoreServiceLoading is disabled. skipping plugin sync.") + return nil + } + if len(installedPlugins) == 0 { return nil } diff --git a/pkg/services/pluginsintegration/installsync/syncer_test.go b/pkg/services/pluginsintegration/installsync/syncer_test.go index a1ca40638b0..e8894436083 100644 --- a/pkg/services/pluginsintegration/installsync/syncer_test.go +++ b/pkg/services/pluginsintegration/installsync/syncer_test.go @@ -20,234 +20,68 @@ import ( "github.com/grafana/grafana/pkg/services/org/orgtest" ) -// Test helpers to avoid import cycles -type fakeServerLock struct { - lockFunc func(ctx context.Context, actionName string, maxInterval time.Duration, fn func(ctx context.Context)) error -} - -func (f *fakeServerLock) LockExecuteAndRelease(ctx context.Context, actionName string, maxInterval time.Duration, fn func(ctx context.Context)) error { - if f.lockFunc != nil { - return f.lockFunc(ctx, actionName, maxInterval, fn) - } - fn(ctx) - return nil -} - -type fakePluginInstallClient struct { - listAllFunc func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) - getFunc func(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.PluginInstall, error) - createFunc func(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.CreateOptions) (*pluginsv0alpha1.PluginInstall, error) - updateFunc func(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.UpdateOptions) (*pluginsv0alpha1.PluginInstall, error) - deleteFunc func(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error -} - -func (f *fakePluginInstallClient) Get(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.PluginInstall, error) { - if f.getFunc != nil { - return f.getFunc(ctx, identifier) - } - // Return a proper k8s NotFound error - return nil, errorsK8s.NewNotFound(schema.GroupResource{ - Group: pluginsv0alpha1.APIGroup, - Resource: "plugininstalls", - }, identifier.Name) -} - -func (f *fakePluginInstallClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) { - if f.listAllFunc != nil { - return f.listAllFunc(ctx, namespace, opts) - } - return &pluginsv0alpha1.PluginInstallList{}, nil -} - -func (f *fakePluginInstallClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) { - return f.ListAll(ctx, namespace, opts) -} - -func (f *fakePluginInstallClient) Create(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.CreateOptions) (*pluginsv0alpha1.PluginInstall, error) { - if f.createFunc != nil { - return f.createFunc(ctx, obj, opts) - } - return obj, nil -} - -func (f *fakePluginInstallClient) Update(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.UpdateOptions) (*pluginsv0alpha1.PluginInstall, error) { - if f.updateFunc != nil { - return f.updateFunc(ctx, obj, opts) - } - return obj, nil -} - -func (f *fakePluginInstallClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus pluginsv0alpha1.PluginInstallStatus, opts resource.UpdateOptions) (*pluginsv0alpha1.PluginInstall, error) { - return nil, nil -} - -func (f *fakePluginInstallClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*pluginsv0alpha1.PluginInstall, error) { - return nil, nil -} - -func (f *fakePluginInstallClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { - if f.deleteFunc != nil { - return f.deleteFunc(ctx, identifier, opts) - } - return nil -} - -type fakeClientGenerator struct { - client *fakePluginInstallClient -} - -func (f *fakeClientGenerator) ClientFor(kind resource.Kind) (resource.Client, error) { - return &fakeResourceClient{client: f.client}, nil -} - -type fakeResourceClient struct { - client *fakePluginInstallClient -} - -func (f *fakeResourceClient) Get(ctx context.Context, identifier resource.Identifier) (resource.Object, error) { - return f.client.Get(ctx, identifier) -} - -func (f *fakeResourceClient) GetInto(ctx context.Context, identifier resource.Identifier, into resource.Object) error { - obj, err := f.client.Get(ctx, identifier) - if err != nil { - return err - } - // Copy the object data into the provided 'into' object - if target, ok := into.(*pluginsv0alpha1.PluginInstall); ok { - *target = *obj - } - return nil -} - -func (f *fakeResourceClient) List(ctx context.Context, namespace string, options resource.ListOptions) (resource.ListObject, error) { - return f.client.ListAll(ctx, namespace, options) -} - -func (f *fakeResourceClient) ListInto(ctx context.Context, namespace string, options resource.ListOptions, into resource.ListObject) error { - list, err := f.client.ListAll(ctx, namespace, options) - if err != nil { - return err - } - // Copy the list data into the provided 'into' object - if target, ok := into.(*pluginsv0alpha1.PluginInstallList); ok { - *target = *list - } - return nil -} - -func (f *fakeResourceClient) Create(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions) (resource.Object, error) { - plugin := obj.(*pluginsv0alpha1.PluginInstall) - return f.client.Create(ctx, plugin, options) -} - -func (f *fakeResourceClient) CreateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions, into resource.Object) error { - created, err := f.Create(ctx, identifier, obj, options) - if err != nil { - return err - } - // Copy the created object data into the provided 'into' object - if plugin, ok := created.(*pluginsv0alpha1.PluginInstall); ok { - if target, ok := into.(*pluginsv0alpha1.PluginInstall); ok { - *target = *plugin - } - } - return nil -} - -func (f *fakeResourceClient) Update(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions) (resource.Object, error) { - plugin := obj.(*pluginsv0alpha1.PluginInstall) - return f.client.Update(ctx, plugin, options) -} - -func (f *fakeResourceClient) UpdateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions, into resource.Object) error { - updated, err := f.Update(ctx, identifier, obj, options) - if err != nil { - return err - } - // Copy the updated object data into the provided 'into' object - if plugin, ok := updated.(*pluginsv0alpha1.PluginInstall); ok { - if target, ok := into.(*pluginsv0alpha1.PluginInstall); ok { - *target = *plugin - } - } - return nil -} - -func (f *fakeResourceClient) Patch(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions) (resource.Object, error) { - return f.client.Patch(ctx, identifier, patch, options) -} - -func (f *fakeResourceClient) PatchInto(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions, into resource.Object) error { - patched, err := f.Patch(ctx, identifier, patch, options) - if err != nil { - return err - } - // Copy the patched object data into the provided 'into' object - if plugin, ok := patched.(*pluginsv0alpha1.PluginInstall); ok { - if target, ok := into.(*pluginsv0alpha1.PluginInstall); ok { - *target = *plugin - } - } - return nil -} - -func (f *fakeResourceClient) Delete(ctx context.Context, identifier resource.Identifier, options resource.DeleteOptions) error { - return f.client.Delete(ctx, identifier, options) -} - -func (f *fakeResourceClient) SubresourceRequest(ctx context.Context, identifier resource.Identifier, req resource.CustomRouteRequestOptions) ([]byte, error) { - return []byte{}, nil -} - -func (f *fakeResourceClient) Watch(ctx context.Context, namespace string, options resource.WatchOptions) (resource.WatchResponse, error) { - return &fakeWatchResponse{}, nil -} - -type fakeWatchResponse struct{} - -func (f *fakeWatchResponse) Stop() {} - -func (f *fakeWatchResponse) WatchEvents() <-chan resource.WatchEvent { - ch := make(chan resource.WatchEvent) - close(ch) - return ch -} - func TestSyncer_Sync(t *testing.T) { tests := []struct { - name string - featureToggleEnabled bool - orgs []*org.OrgDTO - orgServiceError error - serverLockError error - expectedError error - expectSyncCalls int + name string + pluginInstallAPISyncEnabled bool + pluginStoreServiceEnabled bool + installedPlugins []*plugins.Plugin + orgs []*org.OrgDTO + orgServiceError error + serverLockError error + expectedError error + expectSyncCalls int }{ { - name: "feature toggle disabled", - featureToggleEnabled: false, - orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, - expectedError: nil, - expectSyncCalls: 0, + name: "plugin install API sync feature toggle disabled", + pluginInstallAPISyncEnabled: false, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, + expectedError: nil, + expectSyncCalls: 0, }, { - name: "feature toggle enabled, no orgs", - featureToggleEnabled: true, - orgs: []*org.OrgDTO{}, - expectedError: nil, - expectSyncCalls: 0, + name: "plugin store service feature toggle disabled", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: false, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, + expectedError: nil, + expectSyncCalls: 0, }, { - name: "feature toggle enabled, single org", - featureToggleEnabled: true, - orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, - expectedError: nil, - expectSyncCalls: 1, + name: "both feature toggles enabled, no orgs", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: []*org.OrgDTO{}, + expectedError: nil, + expectSyncCalls: 0, }, { - name: "feature toggle enabled, multiple orgs", - featureToggleEnabled: true, + name: "both feature toggles enabled, empty installed plugins", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{}, + orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, + expectedError: nil, + expectSyncCalls: 0, + }, + { + name: "both feature toggles enabled, single org", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, + expectedError: nil, + expectSyncCalls: 1, + }, + { + name: "both feature toggles enabled, multiple orgs", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, orgs: []*org.OrgDTO{ {ID: 1, Name: "Org 1"}, {ID: 2, Name: "Org 2"}, @@ -257,20 +91,24 @@ func TestSyncer_Sync(t *testing.T) { expectSyncCalls: 3, }, { - name: "org service error", - featureToggleEnabled: true, - orgs: nil, - orgServiceError: errors.New("org service error"), - expectedError: errors.New("org service error"), - expectSyncCalls: 0, + name: "org service error", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: nil, + orgServiceError: errors.New("org service error"), + expectedError: errors.New("org service error"), + expectSyncCalls: 0, }, { - name: "server lock error", - featureToggleEnabled: true, - orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, - serverLockError: errors.New("lock error"), - expectedError: errors.New("lock error"), - expectSyncCalls: 0, + name: "server lock error", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, + serverLockError: errors.New("lock error"), + expectedError: errors.New("lock error"), + expectSyncCalls: 0, }, } @@ -280,7 +118,8 @@ func TestSyncer_Sync(t *testing.T) { // Setup feature toggles ft := featuremgmt.NewMockFeatureToggles(t) - ft.EXPECT().IsEnabled(ctx, featuremgmt.FlagPluginInstallAPISync).Return(tt.featureToggleEnabled).Maybe() + ft.EXPECT().IsEnabled(ctx, featuremgmt.FlagPluginInstallAPISync).Return(tt.pluginInstallAPISyncEnabled).Maybe() + ft.EXPECT().IsEnabled(ctx, featuremgmt.FlagPluginStoreServiceLoading).Return(tt.pluginStoreServiceEnabled).Maybe() // Setup org service orgService := orgtest.NewOrgServiceFake() @@ -298,12 +137,12 @@ func TestSyncer_Sync(t *testing.T) { // Setup fake client and registrar syncCalls := 0 fakeClient := &fakePluginInstallClient{ - createFunc: func(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.CreateOptions) (*pluginsv0alpha1.PluginInstall, error) { + createFunc: func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) { syncCalls++ return obj, nil }, - listAllFunc: func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) { - return &pluginsv0alpha1.PluginInstallList{}, nil + listAllFunc: func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) { + return &pluginsv0alpha1.PluginList{}, nil }, } clientGen := &fakeClientGenerator{client: fakeClient} @@ -320,10 +159,7 @@ func TestSyncer_Sync(t *testing.T) { ) // Execute - installedPlugins := []*plugins.Plugin{ - {JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}}, - } - err := s.Sync(ctx, install.SourcePluginStore, installedPlugins) + err := s.Sync(ctx, install.SourcePluginStore, tt.installedPlugins) // Verify if tt.expectedError != nil { @@ -342,7 +178,7 @@ func TestSyncer_syncNamespace(t *testing.T) { tests := []struct { name string installedPlugins []*plugins.Plugin - apiPlugins []pluginsv0alpha1.PluginInstall + apiPlugins []pluginsv0alpha1.Plugin clientListError error expectedError error expectedRegCalls int @@ -353,7 +189,7 @@ func TestSyncer_syncNamespace(t *testing.T) { { name: "no installed plugins, no API plugins", installedPlugins: []*plugins.Plugin{}, - apiPlugins: []pluginsv0alpha1.PluginInstall{}, + apiPlugins: []pluginsv0alpha1.Plugin{}, expectedError: nil, expectedRegCalls: 0, expectedUnregCalls: 0, @@ -364,7 +200,7 @@ func TestSyncer_syncNamespace(t *testing.T) { {JSONData: plugins.JSONData{ID: "plugin-1", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}, {JSONData: plugins.JSONData{ID: "plugin-2", Info: plugins.Info{Version: "2.0.0"}}, Class: plugins.ClassExternal}, }, - apiPlugins: []pluginsv0alpha1.PluginInstall{}, + apiPlugins: []pluginsv0alpha1.Plugin{}, expectedError: nil, expectedRegCalls: 2, expectedUnregCalls: 0, @@ -373,7 +209,7 @@ func TestSyncer_syncNamespace(t *testing.T) { { name: "API plugins only", installedPlugins: []*plugins.Plugin{}, - apiPlugins: []pluginsv0alpha1.PluginInstall{ + apiPlugins: []pluginsv0alpha1.Plugin{ { ObjectMeta: metav1.ObjectMeta{ Name: "plugin-1", @@ -381,7 +217,7 @@ func TestSyncer_syncNamespace(t *testing.T) { install.PluginInstallSourceAnnotation: install.SourcePluginStore, }, }, - Spec: pluginsv0alpha1.PluginInstallSpec{Id: "plugin-1"}, + Spec: pluginsv0alpha1.PluginSpec{Id: "plugin-1"}, }, { ObjectMeta: metav1.ObjectMeta{ @@ -390,7 +226,7 @@ func TestSyncer_syncNamespace(t *testing.T) { install.PluginInstallSourceAnnotation: install.SourcePluginStore, }, }, - Spec: pluginsv0alpha1.PluginInstallSpec{Id: "plugin-2"}, + Spec: pluginsv0alpha1.PluginSpec{Id: "plugin-2"}, }, }, expectedError: nil, @@ -405,7 +241,7 @@ func TestSyncer_syncNamespace(t *testing.T) { {JSONData: plugins.JSONData{ID: "plugin-2", Info: plugins.Info{Version: "2.0.0"}}, Class: plugins.ClassExternal}, {JSONData: plugins.JSONData{ID: "plugin-3", Info: plugins.Info{Version: "3.0.0"}}, Class: plugins.ClassExternal}, }, - apiPlugins: []pluginsv0alpha1.PluginInstall{ + apiPlugins: []pluginsv0alpha1.Plugin{ { ObjectMeta: metav1.ObjectMeta{ Name: "plugin-2", @@ -413,7 +249,7 @@ func TestSyncer_syncNamespace(t *testing.T) { install.PluginInstallSourceAnnotation: install.SourcePluginStore, }, }, - Spec: pluginsv0alpha1.PluginInstallSpec{Id: "plugin-2", Version: "2.0.0"}, + Spec: pluginsv0alpha1.PluginSpec{Id: "plugin-2", Version: "2.0.0"}, }, { ObjectMeta: metav1.ObjectMeta{ @@ -422,7 +258,7 @@ func TestSyncer_syncNamespace(t *testing.T) { install.PluginInstallSourceAnnotation: install.SourcePluginStore, }, }, - Spec: pluginsv0alpha1.PluginInstallSpec{Id: "plugin-4"}, + Spec: pluginsv0alpha1.PluginSpec{Id: "plugin-4"}, }, }, expectedError: nil, @@ -434,7 +270,7 @@ func TestSyncer_syncNamespace(t *testing.T) { { name: "list error", installedPlugins: []*plugins.Plugin{}, - apiPlugins: []pluginsv0alpha1.PluginInstall{}, + apiPlugins: []pluginsv0alpha1.Plugin{}, clientListError: errors.New("list error"), expectedError: errors.New("list error"), }, @@ -450,15 +286,15 @@ func TestSyncer_syncNamespace(t *testing.T) { // Setup fake client fakeClient := &fakePluginInstallClient{ - listAllFunc: func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) { + listAllFunc: func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) { if tt.clientListError != nil { return nil, tt.clientListError } - return &pluginsv0alpha1.PluginInstallList{ + return &pluginsv0alpha1.PluginList{ Items: tt.apiPlugins, }, nil }, - createFunc: func(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.CreateOptions) (*pluginsv0alpha1.PluginInstall, error) { + createFunc: func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) { registeredIDs = append(registeredIDs, obj.Spec.Id) return obj, nil }, @@ -466,7 +302,7 @@ func TestSyncer_syncNamespace(t *testing.T) { unregisteredIDs = append(unregisteredIDs, identifier.Name) return nil }, - getFunc: func(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.PluginInstall, error) { + getFunc: func(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.Plugin, error) { // Check if plugin exists in apiPlugins for i := range tt.apiPlugins { if tt.apiPlugins[i].Name == identifier.Name { @@ -521,7 +357,7 @@ func TestSyncer_syncNamespace(t *testing.T) { } } -func TestSyncer_getClient(t *testing.T) { +func TestInstallRegistrar_GetClient(t *testing.T) { tests := []struct { name string }{ @@ -559,92 +395,196 @@ func TestSyncer_getClient(t *testing.T) { } } -func TestSyncer_syncAllNamespaces(t *testing.T) { - tests := []struct { - name string - orgs []*org.OrgDTO - orgServiceError error - expectedError error - expectedCalls int - }{ - { - name: "no orgs", - orgs: []*org.OrgDTO{}, - expectedError: nil, - expectedCalls: 0, - }, - { - name: "single org", - orgs: []*org.OrgDTO{ - {ID: 1, Name: "Org 1"}, - }, - expectedError: nil, - expectedCalls: 1, - }, - { - name: "multiple orgs", - orgs: []*org.OrgDTO{ - {ID: 1, Name: "Org 1"}, - {ID: 2, Name: "Org 2"}, - {ID: 3, Name: "Org 3"}, - }, - expectedError: nil, - expectedCalls: 3, - }, - { - name: "org service error", - orgs: nil, - orgServiceError: errors.New("org service error"), - expectedError: errors.New("org service error"), - expectedCalls: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() - - orgService := orgtest.NewOrgServiceFake() - orgService.ExpectedOrgs = tt.orgs - orgService.ExpectedError = tt.orgServiceError - - // Track namespace sync calls - syncCalls := 0 - fakeClient := &fakePluginInstallClient{ - createFunc: func(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.CreateOptions) (*pluginsv0alpha1.PluginInstall, error) { - syncCalls++ - return obj, nil - }, - listAllFunc: func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) { - return &pluginsv0alpha1.PluginInstallList{}, nil - }, - } - - clientGen := &fakeClientGenerator{client: fakeClient} - - s := newSyncer( - featuremgmt.NewMockFeatureToggles(t), - clientGen, - install.NewInstallRegistrar(clientGen), - orgService, - func(orgID int64) string { return "org-1" }, - &fakeServerLock{}, - ) - - installedPlugins := []*plugins.Plugin{ - {JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}, - } - - err := s.syncAllNamespaces(ctx, install.SourcePluginStore, installedPlugins) - - if tt.expectedError != nil { - require.Error(t, err) - require.Equal(t, tt.expectedError.Error(), err.Error()) - } else { - require.NoError(t, err) - } - - require.Equal(t, tt.expectedCalls, syncCalls) - }) +// Test helpers to avoid import cycles +type fakeServerLock struct { + lockFunc func(ctx context.Context, actionName string, maxInterval time.Duration, fn func(ctx context.Context)) error +} + +func (f *fakeServerLock) LockExecuteAndRelease(ctx context.Context, actionName string, maxInterval time.Duration, fn func(ctx context.Context)) error { + if f.lockFunc != nil { + return f.lockFunc(ctx, actionName, maxInterval, fn) + } + fn(ctx) + return nil +} + +type fakePluginInstallClient struct { + listAllFunc func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) + getFunc func(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.Plugin, error) + createFunc func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) + updateFunc func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) + deleteFunc func(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error +} + +func (f *fakePluginInstallClient) Get(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.Plugin, error) { + if f.getFunc != nil { + return f.getFunc(ctx, identifier) + } + // Return a proper k8s NotFound error + return nil, errorsK8s.NewNotFound(schema.GroupResource{ + Group: pluginsv0alpha1.APIGroup, + Resource: "plugininstalls", + }, identifier.Name) +} + +func (f *fakePluginInstallClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) { + if f.listAllFunc != nil { + return f.listAllFunc(ctx, namespace, opts) + } + return &pluginsv0alpha1.PluginList{}, nil +} + +func (f *fakePluginInstallClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) { + return f.ListAll(ctx, namespace, opts) +} + +func (f *fakePluginInstallClient) Create(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) { + if f.createFunc != nil { + return f.createFunc(ctx, obj, opts) + } + return obj, nil +} + +func (f *fakePluginInstallClient) Update(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) { + if f.updateFunc != nil { + return f.updateFunc(ctx, obj, opts) + } + return obj, nil +} + +func (f *fakePluginInstallClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus pluginsv0alpha1.PluginStatus, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) { + return nil, nil +} + +func (f *fakePluginInstallClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*pluginsv0alpha1.Plugin, error) { + return nil, nil +} + +func (f *fakePluginInstallClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { + if f.deleteFunc != nil { + return f.deleteFunc(ctx, identifier, opts) + } + return nil +} + +type fakeClientGenerator struct { + client *fakePluginInstallClient +} + +func (f *fakeClientGenerator) ClientFor(kind resource.Kind) (resource.Client, error) { + return &fakeResourceClient{client: f.client}, nil +} + +type fakeResourceClient struct { + client *fakePluginInstallClient +} + +func (f *fakeResourceClient) Get(ctx context.Context, identifier resource.Identifier) (resource.Object, error) { + return f.client.Get(ctx, identifier) +} + +func (f *fakeResourceClient) GetInto(ctx context.Context, identifier resource.Identifier, into resource.Object) error { + obj, err := f.client.Get(ctx, identifier) + if err != nil { + return err + } + // Copy the object data into the provided 'into' object + if target, ok := into.(*pluginsv0alpha1.Plugin); ok { + *target = *obj } + return nil +} + +func (f *fakeResourceClient) List(ctx context.Context, namespace string, options resource.ListOptions) (resource.ListObject, error) { + return f.client.ListAll(ctx, namespace, options) +} + +func (f *fakeResourceClient) ListInto(ctx context.Context, namespace string, options resource.ListOptions, into resource.ListObject) error { + list, err := f.client.ListAll(ctx, namespace, options) + if err != nil { + return err + } + // Copy the list data into the provided 'into' object + if target, ok := into.(*pluginsv0alpha1.PluginList); ok { + *target = *list + } + return nil +} + +func (f *fakeResourceClient) Create(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions) (resource.Object, error) { + plugin := obj.(*pluginsv0alpha1.Plugin) + return f.client.Create(ctx, plugin, options) +} + +func (f *fakeResourceClient) CreateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions, into resource.Object) error { + created, err := f.Create(ctx, identifier, obj, options) + if err != nil { + return err + } + // Copy the created object data into the provided 'into' object + if plugin, ok := created.(*pluginsv0alpha1.Plugin); ok { + if target, ok := into.(*pluginsv0alpha1.Plugin); ok { + *target = *plugin + } + } + return nil +} + +func (f *fakeResourceClient) Update(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions) (resource.Object, error) { + plugin := obj.(*pluginsv0alpha1.Plugin) + return f.client.Update(ctx, plugin, options) +} + +func (f *fakeResourceClient) UpdateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions, into resource.Object) error { + updated, err := f.Update(ctx, identifier, obj, options) + if err != nil { + return err + } + // Copy the updated object data into the provided 'into' object + if plugin, ok := updated.(*pluginsv0alpha1.Plugin); ok { + if target, ok := into.(*pluginsv0alpha1.Plugin); ok { + *target = *plugin + } + } + return nil +} + +func (f *fakeResourceClient) Patch(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions) (resource.Object, error) { + return f.client.Patch(ctx, identifier, patch, options) +} + +func (f *fakeResourceClient) PatchInto(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions, into resource.Object) error { + patched, err := f.Patch(ctx, identifier, patch, options) + if err != nil { + return err + } + // Copy the patched object data into the provided 'into' object + if plugin, ok := patched.(*pluginsv0alpha1.Plugin); ok { + if target, ok := into.(*pluginsv0alpha1.Plugin); ok { + *target = *plugin + } + } + return nil +} + +func (f *fakeResourceClient) Delete(ctx context.Context, identifier resource.Identifier, options resource.DeleteOptions) error { + return f.client.Delete(ctx, identifier, options) +} + +func (f *fakeResourceClient) SubresourceRequest(ctx context.Context, identifier resource.Identifier, req resource.CustomRouteRequestOptions) ([]byte, error) { + return []byte{}, nil +} + +func (f *fakeResourceClient) Watch(ctx context.Context, namespace string, options resource.WatchOptions) (resource.WatchResponse, error) { + return &fakeWatchResponse{}, nil +} + +type fakeWatchResponse struct{} + +func (f *fakeWatchResponse) Stop() {} + +func (f *fakeWatchResponse) WatchEvents() <-chan resource.WatchEvent { + ch := make(chan resource.WatchEvent) + close(ch) + return ch } diff --git a/pkg/tests/apis/plugins/discovery_test.go b/pkg/tests/apis/plugins/discovery_test.go index 545f2f3bf7d..168aa259dad 100644 --- a/pkg/tests/apis/plugins/discovery_test.go +++ b/pkg/tests/apis/plugins/discovery_test.go @@ -20,19 +20,30 @@ func TestIntegrationPluginsIntegrationDiscovery(t *testing.T) { "freshness": "Current", "resources": [ { - "resource": "plugininstalls", + "resource": "plugins", "responseKind": { "group": "", - "kind": "PluginInstall", + "kind": "Plugin", "version": "" }, "scope": "Namespaced", - "singularResource": "plugininstalls", + "singularResource": "plugins", "subresources": [ { "responseKind": { "group": "", - "kind": "PluginInstall", + "kind": "ResourceCallOptions", + "version": "" + }, + "subresource": "meta", + "verbs": [ + "get" + ] + }, + { + "responseKind": { + "group": "", + "kind": "Plugin", "version": "" }, "subresource": "status", @@ -53,35 +64,6 @@ func TestIntegrationPluginsIntegrationDiscovery(t *testing.T) { "update", "watch" ] - }, - { - "resource": "pluginmetas", - "responseKind": { - "group": "", - "kind": "PluginMeta", - "version": "" - }, - "scope": "Namespaced", - "singularResource": "pluginmeta", - "subresources": [ - { - "responseKind": { - "group": "", - "kind": "PluginMeta", - "version": "" - }, - "subresource": "status", - "verbs": [ - "get", - "patch", - "update" - ] - } - ], - "verbs": [ - "get", - "list" - ] } ] } diff --git a/pkg/tests/apis/plugins/plugininstalls_test.go b/pkg/tests/apis/plugins/plugininstalls_test.go index a9ceecfd7aa..d8c45713337 100644 --- a/pkg/tests/apis/plugins/plugininstalls_test.go +++ b/pkg/tests/apis/plugins/plugininstalls_test.go @@ -16,88 +16,54 @@ import ( "github.com/grafana/grafana/pkg/util/testutil" ) -var gvrPluginInstalls = schema.GroupVersionResource{ +var gvrPlugins = schema.GroupVersionResource{ Group: "plugins.grafana.app", Version: "v0alpha1", - Resource: "plugininstalls", + Resource: "plugins", } func TestMain(m *testing.M) { testsuite.Run(m) } -func TestIntegrationPluginInstalls(t *testing.T) { +func TestIntegrationPlugins(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) - t.Run("create plugin install", func(t *testing.T) { + t.Run("create plugin", func(t *testing.T) { helper := setupHelper(t) ctx := context.Background() client := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) pluginName := "test-plugin-create" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "%s"}, "spec": {"version": "1.0.0"} }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + created, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) require.NoError(t, err) require.NotNil(t, created) require.Equal(t, pluginName, created.GetName()) }) - t.Run("create plugin install with status is ignored", func(t *testing.T) { - t.Skip("status is not ignored on create. this might require a change in the SDK. skipping for now") - helper := setupHelper(t) - ctx := context.Background() - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: helper.Org1.Admin, - GVR: gvrPluginInstalls, - }) - pluginName := "test-plugin-create-with-status" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ - "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", - "metadata": {"name": "%s"}, - "spec": {"version": "1.0.0"}, - "status": { - "operatorStates": { - "test-operator": { - "lastEvaluation": "1", - "state": "success" - } - } - } - }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, created) - require.Equal(t, pluginName, created.GetName()) - // Status should be empty as it's ignored on create - status, found, err := unstructured.NestedMap(created.Object, "status") - require.NoError(t, err) - require.True(t, found) // status field should exist - require.Empty(t, status) // but it should be empty - }) - t.Run("get plugin install", func(t *testing.T) { helper := setupHelper(t) ctx := context.Background() client := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) pluginName := "test-plugin-get" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "%s"}, "spec": {"version": "1.0.0"} }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + created, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) require.NoError(t, err) fetched, err := client.Resource.Get(ctx, pluginName, metav1.GetOptions{}) require.NoError(t, err) @@ -111,16 +77,16 @@ func TestIntegrationPluginInstalls(t *testing.T) { ctx := context.Background() client := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) pluginName := "test-plugin-update" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "%s"}, "spec": {"version": "1.0.0"} }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + created, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) require.NoError(t, err) updatedSpec := created.DeepCopy() updatedSpec.Object["spec"] = map[string]interface{}{ @@ -132,116 +98,21 @@ func TestIntegrationPluginInstalls(t *testing.T) { require.Equal(t, "2.0.0", updated.Object["spec"].(map[string]interface{})["version"]) }) - t.Run("update plugin install with status is ignored", func(t *testing.T) { - helper := setupHelper(t) - ctx := context.Background() - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: helper.Org1.Admin, - GVR: gvrPluginInstalls, - }) - pluginName := "test-plugin-update-with-status" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ - "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", - "metadata": {"name": "%s"}, - "spec": {"version": "1.0.0"} - }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) - require.NoError(t, err) - - // Try to update the status via a normal update - withStatus := created.DeepCopy() - withStatus.Object["status"] = map[string]interface{}{ - "operatorStates": map[string]interface{}{ - "test-operator": map[string]interface{}{ - "lastEvaluation": "1", - "state": "success", - }, - }, - } - updated, err := client.Resource.Update(ctx, withStatus, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, updated) - - // The status should not have been updated - status, found, err := unstructured.NestedMap(updated.Object, "status") - require.NoError(t, err) - require.True(t, found) - require.Empty(t, status) - - // also check with get - fetched, err := client.Resource.Get(ctx, pluginName, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, fetched) - status, found, err = unstructured.NestedMap(fetched.Object, "status") - require.NoError(t, err) - require.True(t, found) - require.Empty(t, status) - }) - - t.Run("update plugin install status", func(t *testing.T) { - helper := setupHelper(t) - ctx := context.Background() - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: helper.Org1.Admin, - GVR: gvrPluginInstalls, - }) - pluginName := "test-plugin-status" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ - "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", - "metadata": {"name": "%s"}, - "spec": {"version": "1.0.0"} - }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) - require.NoError(t, err) - - // Update the status - status := created.DeepCopy() - statusPayload := map[string]interface{}{ - "operatorStates": map[string]interface{}{ - "test-operator": map[string]interface{}{ - "lastEvaluation": "1", - "state": "success", - }, - }, - } - status.Object["status"] = statusPayload - updated, err := client.Resource.UpdateStatus(ctx, status, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, updated) - - // Check the status on the returned object - actualStatus, found, err := unstructured.NestedMap(updated.Object, "status") - require.NoError(t, err) - require.True(t, found) - require.Equal(t, statusPayload, actualStatus) - - // Get the status to ensure it persisted - fetched, err := client.Resource.Get(ctx, pluginName, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, fetched) - actualStatus, found, err = unstructured.NestedMap(fetched.Object, "status") - require.NoError(t, err) - require.True(t, found) - require.Equal(t, statusPayload, actualStatus) - }) - t.Run("list plugin installs", func(t *testing.T) { helper := setupHelper(t) ctx := context.Background() client := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) pluginName := "test-plugin-list" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "%s"}, "spec": {"version": "1.0.0"} }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + created, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) require.NoError(t, err) list, err := client.Resource.List(ctx, metav1.ListOptions{}) require.NoError(t, err) @@ -254,16 +125,16 @@ func TestIntegrationPluginInstalls(t *testing.T) { ctx := context.Background() client := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) pluginName := "test-plugin-delete" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "%s"}, "spec": {"version": "1.0.0"} }`, pluginName)) - _, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + _, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) require.NoError(t, err) err = client.Resource.Delete(ctx, pluginName, metav1.DeleteOptions{}) require.NoError(t, err) @@ -281,15 +152,15 @@ func TestIntegrationPluginInstalls(t *testing.T) { t.Run(fmt.Sprintf("with basic role: %s", user.Identity.GetOrgRole()), func(t *testing.T) { client := helper.GetResourceClient(apis.ResourceClientArgs{ User: user, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) - pluginInstall := helper.LoadYAMLOrJSON(`{ + plugin := helper.LoadYAMLOrJSON(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "test-plugin"}, "spec": {"version": "1.0.0"} }`) - _, err := client.Resource.Create(context.Background(), pluginInstall, metav1.CreateOptions{}) + _, err := client.Resource.Create(context.Background(), plugin, metav1.CreateOptions{}) statusError := helper.AsStatusError(err) require.Equal(t, metav1.StatusReasonForbidden, statusError.Status().Reason) err = client.Resource.Delete(context.Background(), "test-plugin", metav1.DeleteOptions{}) diff --git a/pkg/tests/apis/plugins/pluginsmeta_test.go b/pkg/tests/apis/plugins/pluginsmeta_test.go deleted file mode 100644 index 742dca3effc..00000000000 --- a/pkg/tests/apis/plugins/pluginsmeta_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package plugins - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/grafana/grafana/pkg/tests/apis" - "github.com/grafana/grafana/pkg/util/testutil" -) - -var gvrPluginMeta = schema.GroupVersionResource{ - Group: "plugins.grafana.app", - Version: "v0alpha1", - Resource: "pluginmetas", -} - -func TestIntegrationPluginMeta(t *testing.T) { - testutil.SkipIntegrationTestInShortMode(t) - - t.Run("list plugin metas", func(t *testing.T) { - helper := setupHelper(t) - ctx := context.Background() - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: helper.Org1.Admin, - GVR: gvrPluginMeta, - }) - list, err := client.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, list) - require.Empty(t, list.Items) - }) - - t.Run("get plugin meta", func(t *testing.T) { - helper := setupHelper(t) - ctx := context.Background() - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: helper.Org1.Admin, - GVR: gvrPluginMeta, - }) - _, err := client.Resource.Get(ctx, "example", metav1.GetOptions{}) - require.Error(t, err) - }) -} From 30bd4e7dba1567c41cf15834b5bd336953688d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ida=20=C5=A0tambuk?= Date: Wed, 29 Oct 2025 18:47:33 +0100 Subject: [PATCH 26/72] CloudWatch Logs: Support Log Anomalies query type (#113067) --- .../x/CloudWatchDataQuery_types.gen.ts | 36 +++ pkg/tsdb/cloudwatch/cloudwatch.go | 19 +- .../kinds/dataquery/types_dataquery_gen.go | 45 ++- pkg/tsdb/cloudwatch/log_anomalies_query.go | 146 +++++++++ .../cloudwatch/log_anomalies_query_test.go | 212 +++++++++++++ pkg/tsdb/cloudwatch/log_sync_query_test.go | 2 +- pkg/tsdb/cloudwatch/mocks/logs.go | 6 + pkg/tsdb/cloudwatch/models/api.go | 1 + pkg/tsdb/cloudwatch/test_utils.go | 16 + .../LogsAnomaliesQueryEditor.tsx | 41 +++ .../LogsQueryEditor/LogsQueryEditor.tsx | 61 +++- .../datasource/cloudwatch/dataquery.cue | 22 +- .../datasource/cloudwatch/dataquery.gen.ts | 36 +++ .../datasource/cloudwatch/datasource.test.ts | 4 +- .../datasource/cloudwatch/datasource.ts | 17 +- .../plugins/datasource/cloudwatch/guards.ts | 18 +- .../CloudWatchLogsQueryRunner.test.ts | 300 +++++++++++++++++- .../query-runner/CloudWatchLogsQueryRunner.ts | 132 +++++++- .../plugins/datasource/cloudwatch/types.ts | 1 + 19 files changed, 1080 insertions(+), 35 deletions(-) create mode 100644 pkg/tsdb/cloudwatch/log_anomalies_query.go create mode 100644 pkg/tsdb/cloudwatch/log_anomalies_query_test.go create mode 100644 public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsAnomaliesQueryEditor.tsx diff --git a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts index 2334d1d1efd..61cb1e34254 100644 --- a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts @@ -218,6 +218,11 @@ export interface QueryEditorArrayExpression { export type QueryEditorExpression = (QueryEditorArrayExpression | QueryEditorPropertyExpression | QueryEditorGroupByExpression | QueryEditorFunctionExpression | QueryEditorFunctionParameterExpression | QueryEditorOperatorExpression); +export enum LogsMode { + Anomalies = 'Anomalies', + Insights = 'Insights', +} + export enum LogsQueryLanguage { CWLI = 'CWLI', PPL = 'PPL', @@ -241,6 +246,10 @@ export interface CloudWatchLogsQuery extends common.DataQuery { * Log groups to query */ logGroups?: Array; + /** + * Whether a query is a Logs Insights or Logs Anomalies query + */ + logsMode?: LogsMode; /** * Language used for querying logs, can be CWLI, SQL, or PPL. If empty, the default language is CWLI. */ @@ -265,6 +274,33 @@ export const defaultCloudWatchLogsQuery: Partial = { statsGroups: [], }; +/** + * Shape of a Cloudwatch Logs Anomalies query + */ +export interface CloudWatchLogsAnomaliesQuery extends common.DataQuery { + /** + * Used to filter only the anomalies found by a certain anomaly detector + */ + anomalyDetectionARN?: string; + id: string; + /** + * Whether a query is a Logs Insights or Logs Anomalies query + */ + logsMode?: LogsMode; + /** + * Whether a query is a Metrics, Logs or Annotations query + */ + queryMode?: CloudWatchQueryMode; + /** + * AWS region to query for the logs + */ + region: string; + /** + * Filter to return only anomalies that are 'SUPPRESSED', 'UNSUPPRESSED', or 'ALL' (default) + */ + suppressionState?: string; +} + export interface LogGroup { /** * AccountId of the log group diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index 1015393477b..3ebe95515c0 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/pkg/tsdb/cloudwatch/cloudwatch.go @@ -36,7 +36,7 @@ const ( headerFromAlert = "FromAlert" defaultRegion = "default" - logsQueryMode = "Logs" + queryModeLogs = "Logs" // QueryTypes annotationQuery = "annotationQuery" logAction = "logAction" @@ -45,7 +45,8 @@ const ( type DataQueryJson struct { dataquery.CloudWatchAnnotationQuery - Type string `json:"type,omitempty"` + Type string `json:"type,omitempty"` + LogsMode dataquery.LogsMode `json:"logsMode,omitempty"` } type DataSource struct { @@ -147,12 +148,22 @@ func (ds *DataSource) QueryData(ctx context.Context, req *backend.QueryDataReque if model.QueryMode != "" { queryMode = string(model.QueryMode) } - fromPublicDashboard := model.Type == "" && queryMode == logsQueryMode - isSyncLogQuery := ((fromAlert || fromExpression) && queryMode == logsQueryMode) || fromPublicDashboard + + fromPublicDashboard := model.Type == "" + + isLogInsightsQuery := queryMode == queryModeLogs && (model.LogsMode == "" || model.LogsMode == dataquery.LogsModeInsights) + + isSyncLogQuery := isLogInsightsQuery && ((fromAlert || fromExpression) || fromPublicDashboard) + if isSyncLogQuery { return executeSyncLogQuery(ctx, ds, req) } + isLogsAnomaliesQuery := model.QueryMode == dataquery.CloudWatchQueryModeLogs && model.LogsMode == dataquery.LogsModeAnomalies + if isLogsAnomaliesQuery { + return executeLogAnomaliesQuery(ctx, ds, req) + } + var result *backend.QueryDataResponse switch model.Type { case annotationQuery: diff --git a/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go index 76c706094a6..ce8bc54c6c5 100644 --- a/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go @@ -285,6 +285,13 @@ func NewQueryEditorOperatorValueType() *QueryEditorOperatorValueType { return NewStringOrBoolOrInt64OrArrayOfQueryEditorOperatorType() } +type LogsMode string + +const ( + LogsModeInsights LogsMode = "Insights" + LogsModeAnomalies LogsMode = "Anomalies" +) + type LogsQueryLanguage string const ( @@ -297,7 +304,9 @@ const ( type CloudWatchLogsQuery struct { // Whether a query is a Metrics, Logs, or Annotations query QueryMode CloudWatchQueryMode `json:"queryMode"` - Id string `json:"id"` + // Whether a query is a Logs Insights or Logs Anomalies query + LogsMode *LogsMode `json:"logsMode,omitempty"` + Id string `json:"id"` // AWS region to query for the logs Region string `json:"region"` // The CloudWatch Logs Insights query to execute @@ -347,6 +356,40 @@ func NewLogGroup() *LogGroup { return &LogGroup{} } +// Shape of a Cloudwatch Logs Anomalies query +type CloudWatchLogsAnomaliesQuery struct { + Id string `json:"id"` + // AWS region to query for the logs + Region string `json:"region"` + // Whether a query is a Metrics, Logs or Annotations query + QueryMode *CloudWatchQueryMode `json:"queryMode,omitempty"` + // Whether a query is a Logs Insights or Logs Anomalies query + LogsMode *LogsMode `json:"logsMode,omitempty"` + // Filter to return only anomalies that are 'SUPPRESSED', 'UNSUPPRESSED', or 'ALL' (default) + SuppressionState *string `json:"suppressionState,omitempty"` + // A unique identifier for the query within the list of targets. + // In server side expressions, the refId is used as a variable name to identify results. + // By default, the UI will assign A->Z; however setting meaningful names may be useful. + RefId string `json:"refId"` + // If hide is set to true, Grafana will filter out the response(s) associated with this query before returning it to the panel. + Hide *bool `json:"hide,omitempty"` + // Specify the query flavor + // TODO make this required and give it a default + QueryType *string `json:"queryType,omitempty"` + // Used to filter only the anomalies found by a certain anomaly detector + AnomalyDetectionARN *string `json:"anomalyDetectionARN,omitempty"` + // For mixed data sources the selected datasource is on the query level. + // For non mixed scenarios this is undefined. + // TODO find a better way to do this ^ that's friendly to schema + // TODO this shouldn't be unknown but DataSourceRef | null + Datasource any `json:"datasource,omitempty"` +} + +// NewCloudWatchLogsAnomaliesQuery creates a new CloudWatchLogsAnomaliesQuery object. +func NewCloudWatchLogsAnomaliesQuery() *CloudWatchLogsAnomaliesQuery { + return &CloudWatchLogsAnomaliesQuery{} +} + // Shape of a CloudWatch Annotation query // TS type is CloudWatchDefaultQuery = Omit & CloudWatchMetricsQuery, declared in veneer // #CloudWatchDefaultQuery: #CloudWatchLogsQuery & #CloudWatchMetricsQuery @cuetsy(kind="type") diff --git a/pkg/tsdb/cloudwatch/log_anomalies_query.go b/pkg/tsdb/cloudwatch/log_anomalies_query.go new file mode 100644 index 00000000000..f39b92eeffa --- /dev/null +++ b/pkg/tsdb/cloudwatch/log_anomalies_query.go @@ -0,0 +1,146 @@ +package cloudwatch + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" + + cloudwatchLogsTypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery" +) + +var executeLogAnomaliesQuery = func(ctx context.Context, ds *DataSource, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + resp := backend.NewQueryDataResponse() + + for _, q := range req.Queries { + var anomaliesQuery dataquery.CloudWatchLogsAnomaliesQuery + err := json.Unmarshal(q.JSON, &anomaliesQuery) + if err != nil { + continue + } + + region := anomaliesQuery.Region + if region == "" || region == defaultRegion { + anomaliesQuery.Region = ds.Settings.Region + } + + logsClient, err := ds.getCWLogsClient(ctx, region) + if err != nil { + return nil, err + } + + listAnomaliesInput := &cloudwatchlogs.ListAnomaliesInput{} + if anomaliesQuery.SuppressionState != nil { + listAnomaliesInput.SuppressionState = getSuppressionState(*anomaliesQuery.SuppressionState) + } + + if anomaliesQuery.AnomalyDetectionARN == nil || *anomaliesQuery.AnomalyDetectionARN != "" { + listAnomaliesInput.AnomalyDetectorArn = anomaliesQuery.AnomalyDetectionARN + } + + response, err := logsClient.ListAnomalies(ctx, listAnomaliesInput) + + if err != nil { + result := backend.NewQueryDataResponse() + result.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(backend.DownstreamError(fmt.Errorf("%v: %w", "failed to call cloudwatch:ListAnomalies", err))) + return result, nil + } + + dataframe, err := logsAnomaliesResultsToDataframes(response) + if err != nil { + return nil, err + } + + respD := resp.Responses[q.RefID] + respD.Frames = data.Frames{dataframe} + resp.Responses[q.RefID] = respD + } + + return resp, nil + +} + +func logsAnomaliesResultsToDataframes(response *cloudwatchlogs.ListAnomaliesOutput) (*data.Frame, error) { + frame := data.NewFrame("Log anomalies") + + if len(response.Anomalies) == 0 { + return frame, nil + } + + n := len(response.Anomalies) + anomalyArns := make([]string, n) + descriptions := make([]string, n) + suppressedStatus := make([]bool, n) + + priorities := make([]string, n) + patterns := make([]string, n) + statuses := make([]string, n) + logGroupArnLists := make([]string, n) + firstSeens := make([]time.Time, n) + lastSeens := make([]time.Time, n) + logTrends := make([]*json.RawMessage, n) + + for i, anomaly := range response.Anomalies { + anomalyArns[i] = *anomaly.AnomalyDetectorArn + descriptions[i] = *anomaly.Description + suppressedStatus[i] = *anomaly.Suppressed + priorities[i] = *anomaly.Priority + if anomaly.PatternString != nil { + patterns[i] = *anomaly.PatternString + } + statuses[i] = string(anomaly.State) + logGroupArnLists[i] = strings.Join(anomaly.LogGroupArnList, ",") + + firstSeens[i] = time.UnixMilli(anomaly.FirstSeen) + + lastSeens[i] = time.UnixMilli(anomaly.LastSeen) + + // data.Frame returned from the backend cannot contain fields of type data.Frames + // so histogram is kept as json.RawMessageto be built as sparkline table cell on the FE + histogramField := anomaly.Histogram + histogramJSON, err := json.Marshal(histogramField) + if err != nil { + logTrends[i] = nil + } else { + rawMsg := json.RawMessage(histogramJSON) + logTrends[i] = &rawMsg + } + } + + newFields := make([]*data.Field, 0, len(response.Anomalies)) + + newFields = append(newFields, data.NewField("state", nil, statuses).SetConfig(&data.FieldConfig{DisplayName: "State"})) + newFields = append(newFields, data.NewField("description", nil, descriptions).SetConfig(&data.FieldConfig{DisplayName: "Anomaly"})) + newFields = append(newFields, data.NewField("priority", nil, priorities).SetConfig(&data.FieldConfig{DisplayName: "Priority"})) + newFields = append(newFields, data.NewField("patternString", nil, patterns).SetConfig(&data.FieldConfig{DisplayName: "Log Pattern"})) + // FE expects the field name to be logTrend in order to identify histogram field for sparkline rendering + newFields = append(newFields, data.NewField("logTrend", nil, logTrends).SetConfig(&data.FieldConfig{DisplayName: "Log Trend"})) + newFields = append(newFields, data.NewField("firstSeen", nil, firstSeens).SetConfig(&data.FieldConfig{DisplayName: "First seen"})) + newFields = append(newFields, data.NewField("lastSeen", nil, lastSeens).SetConfig(&data.FieldConfig{DisplayName: "Last seen"})) + newFields = append(newFields, data.NewField("suppressed", nil, suppressedStatus).SetConfig(&data.FieldConfig{DisplayName: "Suppressed?"})) + newFields = append(newFields, data.NewField("logGroupArnList", nil, logGroupArnLists).SetConfig(&data.FieldConfig{DisplayName: "Log Groups"})) + newFields = append(newFields, data.NewField("anomalyArn", nil, anomalyArns).SetConfig(&data.FieldConfig{DisplayName: "Anomaly Arn"})) + + frame.Fields = newFields + setPreferredVisType(frame, data.VisTypeTable) + return frame, nil +} + +func getSuppressionState(suppressionState string) cloudwatchLogsTypes.SuppressionState { + switch suppressionState { + case "suppressed": + return cloudwatchLogsTypes.SuppressionStateSuppressed + case "unsuppressed": + return cloudwatchLogsTypes.SuppressionStateUnsuppressed + case "all": + return "" + default: + return "" + } +} diff --git a/pkg/tsdb/cloudwatch/log_anomalies_query_test.go b/pkg/tsdb/cloudwatch/log_anomalies_query_test.go new file mode 100644 index 00000000000..d9b3b85314d --- /dev/null +++ b/pkg/tsdb/cloudwatch/log_anomalies_query_test.go @@ -0,0 +1,212 @@ +package cloudwatch + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + cloudwatchLogsTypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" + "github.com/stretchr/testify/assert" +) + +func Test_executeLogAnomaliesQuery(t *testing.T) { + origNewCWClient := NewCWClient + t.Cleanup(func() { + NewCWClient = origNewCWClient + }) + + var cli fakeCWLogsClient + NewCWLogsClient = func(aws.Config) models.CWLogsClient { + return &cli + } + + t.Run("getCWLogsClient is called with correct suppression state", func(t *testing.T) { + testcases := []struct { + name string + suppressionStateInQuery string + result cloudwatchLogsTypes.SuppressionState + }{ + { + "suppressed state", + "suppressed", + cloudwatchLogsTypes.SuppressionStateSuppressed, + }, + { + "unsuppressed state", + "unsuppressed", + cloudwatchLogsTypes.SuppressionStateUnsuppressed, + }, + { + "empty state", + "", + "", + }, + { + "all state", + "all", + "", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + cli = fakeCWLogsClient{anomalies: []cloudwatchLogsTypes.Anomaly{}} + ds := newTestDatasource() + + _, err := ds.QueryData(context.Background(), &backend.QueryDataRequest{ + Headers: map[string]string{headerFromAlert: ""}, + PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}}, + Queries: []backend.DataQuery{ + { + TimeRange: backend.TimeRange{From: time.Unix(0, 0), To: time.Unix(1, 0)}, + JSON: json.RawMessage(`{ + "queryMode": "Logs", + "logsMode": "Anomalies", + "suppressionState": "` + tc.suppressionStateInQuery + `", + "region": "us-east-1" + }`), + }, + }, + }) + assert.NoError(t, err) + assert.Equal(t, tc.result, cli.calls.listAnomalies[0].SuppressionState) + }) + } + }) +} + +func Test_executeLogAnomaliesQuery_returns_data_frames(t *testing.T) { + origNewCWClient := NewCWClient + t.Cleanup(func() { + NewCWClient = origNewCWClient + }) + + var cli fakeCWLogsClient + NewCWLogsClient = func(aws.Config) models.CWLogsClient { + return &cli + } + t.Run("returns log anomalies data frames", func(t *testing.T) { + cli = fakeCWLogsClient{anomalies: []cloudwatchLogsTypes.Anomaly{ + { + AnomalyId: aws.String("anomaly-1"), + AnomalyDetectorArn: aws.String("arn:aws:logs:us-east-1:123456789012:anomaly-detector:anomaly-detector-1"), + FirstSeen: 1622505600000, // June 1, 2021 00:00:00 GMT + LastSeen: 1622592000000, // June 2, 2021 00:00:00 GMT + LogGroupArnList: []string{"arn:aws:logs:us-east-1:1234567:log-group-1:id-1", "arn:aws:logs:us-east-1:1234567:log-group-2:id-2"}, + Description: aws.String("Description 1"), + State: cloudwatchLogsTypes.StateActive, + Priority: aws.String("high"), + PatternString: aws.String(`{"ClusterName":"PetSite","Namespace":"default","Service":"service-petsite",,"instance":"instance"-5:Token-6,"job":"kubernetes-service-endpoints","pod_name":"pod_name"-9,"prom_metric_type":"counter"}`), + Suppressed: aws.Bool(false), + Histogram: map[string]int64{ + "1622505600000": 5, + "1622519200000": 10, + "1622532800000": 7, + }, + }, + { + AnomalyId: aws.String("anomaly-2"), + AnomalyDetectorArn: aws.String("arn:aws:logs:us-east-1:123456789012:anomaly-detector:anomaly-detector-2"), + FirstSeen: 1622592000000, // June 2, 2021 00:00:00 GMT + LastSeen: 1622678400000, // June 3, 2021 00:00:00 GMT + LogGroupArnList: []string{"arn:aws:logs:us-east-1:1234567:log-group-1:id-3", "arn:aws:logs:us-east-1:1234567:log-group-2:id-4"}, + Description: aws.String("Description 2"), + State: cloudwatchLogsTypes.StateSuppressed, + Priority: aws.String("low"), + PatternString: aws.String(`{"ClusterName":"PetSite","Namespace":"default","Service":"service-petsite","dotnet_collection_count_total":"dotnet_collection_count_total"-3}`), + Suppressed: aws.Bool(true), + Histogram: map[string]int64{ + "1622592000000": 3, + }, + }, + }} + + ds := newTestDatasource() + + resp, err := ds.QueryData(context.Background(), &backend.QueryDataRequest{ + Headers: map[string]string{headerFromAlert: ""}, + PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}}, + Queries: []backend.DataQuery{ + { + TimeRange: backend.TimeRange{From: time.Unix(0, 0), To: time.Unix(1, 0)}, + JSON: json.RawMessage(`{ + "queryMode": "Logs", + "logsMode": "Anomalies", + "suppressionState": "all", + "region": "us-east-1" + }`), + }, + }, + }) + + assert.NoError(t, err) + assert.Len(t, resp.Responses, 1) + for _, r := range resp.Responses { + assert.Len(t, r.Frames, 1) + frame := r.Frames[0] + assert.Equal(t, "Log anomalies", frame.Name) + assert.Len(t, frame.Fields, 10) + + stateField := frame.Fields[0] + assert.Equal(t, "state", stateField.Name) + assert.Equal(t, "Active", stateField.At(0)) + assert.Equal(t, "Suppressed", stateField.At(1)) + + descriptionField := frame.Fields[1] + assert.Equal(t, "description", descriptionField.Name) + assert.Equal(t, "Description 1", descriptionField.At(0)) + assert.Equal(t, "Description 2", descriptionField.At(1)) + + priorityField := frame.Fields[2] + assert.Equal(t, "priority", priorityField.Name) + assert.Equal(t, "high", priorityField.At(0)) + assert.Equal(t, "low", priorityField.At(1)) + + patternStringField := frame.Fields[3] + assert.Equal(t, "patternString", patternStringField.Name) + assert.Equal(t, `{"ClusterName":"PetSite","Namespace":"default","Service":"service-petsite",,"instance":"instance"-5:Token-6,"job":"kubernetes-service-endpoints","pod_name":"pod_name"-9,"prom_metric_type":"counter"}`, patternStringField.At(0)) + assert.Equal(t, `{"ClusterName":"PetSite","Namespace":"default","Service":"service-petsite","dotnet_collection_count_total":"dotnet_collection_count_total"-3}`, patternStringField.At(1)) + + histogramField := frame.Fields[4] + assert.Equal(t, "logTrend", histogramField.Name) + + histogram0 := histogramField.At(0).(*json.RawMessage) + var histData0 map[string]int64 + err = json.Unmarshal(*histogram0, &histData0) + assert.NoError(t, err) + assert.Equal(t, int64(5), histData0["1622505600000"]) + assert.Equal(t, int64(10), histData0["1622519200000"]) + assert.Equal(t, int64(7), histData0["1622532800000"]) + + firstSeenField := frame.Fields[5] + assert.Equal(t, "firstSeen", firstSeenField.Name) + assert.Equal(t, time.Unix(1622505600, 0), firstSeenField.At(0)) + assert.Equal(t, time.Unix(1622592000, 0), firstSeenField.At(1)) + + lastSeenField := frame.Fields[6] + assert.Equal(t, "lastSeen", lastSeenField.Name) + assert.Equal(t, time.Unix(1622592000, 0), lastSeenField.At(0)) + assert.Equal(t, time.Unix(1622678400, 0), lastSeenField.At(1)) + + suppressedField := frame.Fields[7] + assert.Equal(t, "suppressed", suppressedField.Name) + assert.Equal(t, false, suppressedField.At(0)) + assert.Equal(t, true, suppressedField.At(1)) + + logGroupArnListField := frame.Fields[8] + assert.Equal(t, "logGroupArnList", logGroupArnListField.Name) + assert.Equal(t, "arn:aws:logs:us-east-1:1234567:log-group-1:id-1,arn:aws:logs:us-east-1:1234567:log-group-2:id-2", logGroupArnListField.At(0)) + assert.Equal(t, "arn:aws:logs:us-east-1:1234567:log-group-1:id-3,arn:aws:logs:us-east-1:1234567:log-group-2:id-4", logGroupArnListField.At(1)) + + anomalyDetectorArnField := frame.Fields[9] + assert.Equal(t, "anomalyArn", anomalyDetectorArnField.Name) + assert.Equal(t, "arn:aws:logs:us-east-1:123456789012:anomaly-detector:anomaly-detector-1", anomalyDetectorArnField.At(0)) + assert.Equal(t, "arn:aws:logs:us-east-1:123456789012:anomaly-detector:anomaly-detector-2", anomalyDetectorArnField.At(1)) + } + }) +} diff --git a/pkg/tsdb/cloudwatch/log_sync_query_test.go b/pkg/tsdb/cloudwatch/log_sync_query_test.go index b1c0b99175e..376464d9340 100644 --- a/pkg/tsdb/cloudwatch/log_sync_query_test.go +++ b/pkg/tsdb/cloudwatch/log_sync_query_test.go @@ -137,7 +137,7 @@ func Test_executeSyncLogQuery(t *testing.T) { executeSyncLogQuery = origExecuteSyncLogQuery }) - t.Run("when query mode is 'Logs' and does not include type or subtype", func(t *testing.T) { + t.Run("when query mode is 'Logs Insights' and does not include type or subtype", func(t *testing.T) { origExecuteSyncLogQuery := executeSyncLogQuery syncCalled := false executeSyncLogQuery = func(ctx context.Context, e *DataSource, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { diff --git a/pkg/tsdb/cloudwatch/mocks/logs.go b/pkg/tsdb/cloudwatch/mocks/logs.go index 7843fdaacd1..da8dd2cc47c 100644 --- a/pkg/tsdb/cloudwatch/mocks/logs.go +++ b/pkg/tsdb/cloudwatch/mocks/logs.go @@ -66,3 +66,9 @@ func (m *MockLogEvents) GetLogEvents(ctx context.Context, input *cloudwatchlogs. return args.Get(0).(*cloudwatchlogs.GetLogEventsOutput), args.Error(1) } + +func (m *MockLogEvents) ListAnomalies(ctx context.Context, input *cloudwatchlogs.ListAnomaliesInput, optFns ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.ListAnomaliesOutput, error) { + args := m.Called(ctx, input, optFns) + + return args.Get(0).(*cloudwatchlogs.ListAnomaliesOutput), args.Error(1) +} diff --git a/pkg/tsdb/cloudwatch/models/api.go b/pkg/tsdb/cloudwatch/models/api.go index e4b487288f1..eed0243b69d 100644 --- a/pkg/tsdb/cloudwatch/models/api.go +++ b/pkg/tsdb/cloudwatch/models/api.go @@ -76,6 +76,7 @@ type CWLogsClient interface { cloudwatchlogs.GetLogEventsAPIClient cloudwatchlogs.DescribeLogGroupsAPIClient + cloudwatchlogs.ListAnomaliesAPIClient } type CWClient interface { diff --git a/pkg/tsdb/cloudwatch/test_utils.go b/pkg/tsdb/cloudwatch/test_utils.go index 82ce33099ee..3f4c797dea4 100644 --- a/pkg/tsdb/cloudwatch/test_utils.go +++ b/pkg/tsdb/cloudwatch/test_utils.go @@ -29,12 +29,15 @@ type fakeCWLogsClient struct { queryResults cloudwatchlogs.GetQueryResultsOutput logGroupsIndex int + + anomalies []cloudwatchlogstypes.Anomaly } type logsQueryCalls struct { startQuery []*cloudwatchlogs.StartQueryInput getEvents []*cloudwatchlogs.GetLogEventsInput describeLogGroups []*cloudwatchlogs.DescribeLogGroupsInput + listAnomalies []*cloudwatchlogs.ListAnomaliesInput } func (m *fakeCWLogsClient) GetQueryResults(_ context.Context, _ *cloudwatchlogs.GetQueryResultsInput, _ ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.GetQueryResultsOutput, error) { @@ -55,6 +58,14 @@ func (m *fakeCWLogsClient) StopQuery(_ context.Context, _ *cloudwatchlogs.StopQu }, nil } +func (m *fakeCWLogsClient) ListAnomalies(_ context.Context, input *cloudwatchlogs.ListAnomaliesInput, _ ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.ListAnomaliesOutput, error) { + m.calls.listAnomalies = append(m.calls.listAnomalies, input) + + return &cloudwatchlogs.ListAnomaliesOutput{ + Anomalies: m.anomalies, + }, nil +} + type mockLogsSyncClient struct { mock.Mock } @@ -80,6 +91,11 @@ func (m *mockLogsSyncClient) StartQuery(ctx context.Context, input *cloudwatchlo return args.Get(0).(*cloudwatchlogs.StartQueryOutput), args.Error(1) } +func (m *mockLogsSyncClient) ListAnomalies(ctx context.Context, input *cloudwatchlogs.ListAnomaliesInput, optFns ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.ListAnomaliesOutput, error) { + args := m.Called(ctx, input, optFns) + return args.Get(0).(*cloudwatchlogs.ListAnomaliesOutput), args.Error(1) +} + func (m *fakeCWLogsClient) DescribeLogGroups(_ context.Context, input *cloudwatchlogs.DescribeLogGroupsInput, _ ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { m.calls.describeLogGroups = append(m.calls.describeLogGroups, input) output := &m.logGroups[m.logGroupsIndex] diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsAnomaliesQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsAnomaliesQueryEditor.tsx new file mode 100644 index 00000000000..218d16e6b98 --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsAnomaliesQueryEditor.tsx @@ -0,0 +1,41 @@ +import { EditorField, EditorRow } from '@grafana/plugin-ui'; +import { Combobox, Input } from '@grafana/ui'; + +import { CloudWatchLogsAnomaliesQuery } from '../../../dataquery.gen'; + +interface Props { + query: CloudWatchLogsAnomaliesQuery; + onChange: (value: CloudWatchLogsAnomaliesQuery) => void; +} + +const supressionStateOptions = [ + { label: 'All', value: 'all' }, + { label: 'Suppressed', value: 'suppressed' }, + { label: 'Unsuppressed', value: 'unsuppressed' }, +]; + +export const LogsAnomaliesQueryEditor = (props: Props) => { + return ( + <> + + + { + props.onChange({ ...props.query, anomalyDetectionARN: e.currentTarget.value }); + }} + /> + + + { + props.onChange({ ...props.query, suppressionState: e.value }); + }} + /> + + + + ); +}; diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx index 6be68db99cb..bf59c959066 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/LogsQueryEditor/LogsQueryEditor.tsx @@ -6,9 +6,10 @@ import { InlineSelect } from '@grafana/plugin-ui'; import { CloudWatchDatasource } from '../../../datasource'; import { DEFAULT_CWLI_QUERY_STRING, DEFAULT_PPL_QUERY_STRING, DEFAULT_SQL_QUERY_STRING } from '../../../defaultQueries'; -import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery, LogsQueryLanguage } from '../../../types'; +import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery, LogsMode, LogsQueryLanguage } from '../../../types'; import { CloudWatchLink } from './CloudWatchLink'; +import { LogsAnomaliesQueryEditor } from './LogsAnomaliesQueryEditor'; import { CloudWatchLogsQueryField } from './LogsQueryField'; type Props = QueryEditorProps & { @@ -22,6 +23,11 @@ const logsQueryLanguageOptions: Array> = [ { label: 'OpenSearch PPL', value: LogsQueryLanguage.PPL }, ]; +const logsModeOptions: Array> = [ + { label: 'Logs Insights', value: LogsMode.Insights }, + { label: 'Logs Anomalies', value: LogsMode.Anomalies }, +]; + export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor(props: Props) { const { query, data, datasource, onChange, extraHeaderElementLeft } = props; @@ -42,6 +48,13 @@ export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor [isQueryNew, onChange, query] ); + const onLogsModeChange = useCallback( + (logsMode: LogsMode | undefined) => { + onChange({ ...query, logsMode }); + }, + [query, onChange] + ); + // if the query has already been saved from before, we shouldn't replace it with a default one useEffectOnce(() => { if (query.expression) { @@ -58,20 +71,32 @@ export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor useEffect(() => { extraHeaderElementLeft?.( - { - onQueryLanguageChange(value); - }} - /> + <> + { + onLogsModeChange(value); + }} + /> + {query.logsMode !== LogsMode.Anomalies && ( + { + onQueryLanguageChange(value); + }} + /> + )} + ); return () => { extraHeaderElementLeft?.(undefined); }; - }, [extraHeaderElementLeft, onChange, onQueryLanguageChange, query]); + }, [extraHeaderElementLeft, onChange, onQueryLanguageChange, query, onLogsModeChange]); const onQueryStringChange = (query: CloudWatchQuery) => { onChange(query); @@ -79,11 +104,17 @@ export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor }; return ( - } - /> + <> + {query.logsMode === LogsMode.Anomalies ? ( + + ) : ( + } + /> + )} + ); }); diff --git a/public/app/plugins/datasource/cloudwatch/dataquery.cue b/public/app/plugins/datasource/cloudwatch/dataquery.cue index a0afcfb729f..4144b423419 100644 --- a/public/app/plugins/datasource/cloudwatch/dataquery.cue +++ b/public/app/plugins/datasource/cloudwatch/dataquery.cue @@ -146,7 +146,7 @@ composableKinds: DataQuery: { } @cuetsy(kind="interface") #QueryEditorExpression: #QueryEditorArrayExpression | #QueryEditorPropertyExpression | #QueryEditorGroupByExpression | #QueryEditorFunctionExpression | #QueryEditorFunctionParameterExpression | #QueryEditorOperatorExpression @cuetsy(kind="type") - + #LogsMode: "Insights" | "Anomalies" @cuetsy(kind="enum") #LogsQueryLanguage: "CWLI" | "SQL" | "PPL" @cuetsy(kind="enum") // Shape of a CloudWatch Logs query @@ -155,6 +155,8 @@ composableKinds: DataQuery: { // Whether a query is a Metrics, Logs, or Annotations query queryMode: #CloudWatchQueryMode + // Whether a query is a Logs Insights or Logs Anomalies query + logsMode?: #LogsMode id: string // AWS region to query for the logs region: string @@ -166,9 +168,27 @@ composableKinds: DataQuery: { logGroups?: [...#LogGroup] // @deprecated use logGroups logGroupNames?: [...string] + // Language used for querying logs, can be CWLI, SQL, or PPL. If empty, the default language is CWLI. queryLanguage?: #LogsQueryLanguage } @cuetsy(kind="interface") + + // Shape of a Cloudwatch Logs Anomalies query + #CloudWatchLogsAnomaliesQuery: { + common.DataQuery + id: string + // AWS region to query for the logs + region: string + // Whether a query is a Metrics, Logs or Annotations query + queryMode?: #CloudWatchQueryMode + // Whether a query is a Logs Insights or Logs Anomalies query + logsMode?: #LogsMode + // Filter to return only anomalies that are 'SUPPRESSED', 'UNSUPPRESSED', or 'ALL' (default) + suppressionState?: string + // Used to filter only the anomalies found by a certain anomaly detector + anomalyDetectionARN?: string + } @cuetsy(kind="interface") + #LogGroup: { // ARN of the log group arn: string diff --git a/public/app/plugins/datasource/cloudwatch/dataquery.gen.ts b/public/app/plugins/datasource/cloudwatch/dataquery.gen.ts index 461ca94247e..ba0a1134d2d 100644 --- a/public/app/plugins/datasource/cloudwatch/dataquery.gen.ts +++ b/public/app/plugins/datasource/cloudwatch/dataquery.gen.ts @@ -216,6 +216,11 @@ export interface QueryEditorArrayExpression { export type QueryEditorExpression = (QueryEditorArrayExpression | QueryEditorPropertyExpression | QueryEditorGroupByExpression | QueryEditorFunctionExpression | QueryEditorFunctionParameterExpression | QueryEditorOperatorExpression); +export enum LogsMode { + Anomalies = 'Anomalies', + Insights = 'Insights', +} + export enum LogsQueryLanguage { CWLI = 'CWLI', PPL = 'PPL', @@ -239,6 +244,10 @@ export interface CloudWatchLogsQuery extends common.DataQuery { * Log groups to query */ logGroups?: Array; + /** + * Whether a query is a Logs Insights or Logs Anomalies query + */ + logsMode?: LogsMode; /** * Language used for querying logs, can be CWLI, SQL, or PPL. If empty, the default language is CWLI. */ @@ -263,6 +272,33 @@ export const defaultCloudWatchLogsQuery: Partial = { statsGroups: [], }; +/** + * Shape of a Cloudwatch Logs Anomalies query + */ +export interface CloudWatchLogsAnomaliesQuery extends common.DataQuery { + /** + * Used to filter only the anomalies found by a certain anomaly detector + */ + anomalyDetectionARN?: string; + id: string; + /** + * Whether a query is a Logs Insights or Logs Anomalies query + */ + logsMode?: LogsMode; + /** + * Whether a query is a Metrics, Logs or Annotations query + */ + queryMode?: CloudWatchQueryMode; + /** + * AWS region to query for the logs + */ + region: string; + /** + * Filter to return only anomalies that are 'SUPPRESSED', 'UNSUPPRESSED', or 'ALL' (default) + */ + suppressionState?: string; +} + export interface LogGroup { /** * AccountId of the log group diff --git a/public/app/plugins/datasource/cloudwatch/datasource.test.ts b/public/app/plugins/datasource/cloudwatch/datasource.test.ts index dc15557ee8a..3186c92121c 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.test.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.test.ts @@ -306,7 +306,7 @@ describe('datasource', () => { }); }); - it('should add a data link field to log queries', async () => { + it('should add links to log insights queries', async () => { const { datasource } = setupForLogs(); const observable = datasource.query({ @@ -439,7 +439,7 @@ describe('datasource', () => { const { datasource } = setupMockedDataSource(); expect(datasource.getDefaultQuery(CoreApp.PanelEditor).queryMode).toEqual('Metrics'); }); - it('should set default log groups in default query', () => { + it('should set default log groups in default logs insights query', () => { const { datasource } = setupMockedDataSource({ customInstanceSettings: { ...CloudWatchSettings, diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index 685dce3c15d..27c240dd6cd 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -16,7 +16,12 @@ import { DataSourceWithBackend, TemplateSrv, getTemplateSrv } from '@grafana/run import { CloudWatchAnnotationSupport } from './annotationSupport'; import { DEFAULT_METRICS_QUERY, getDefaultLogsQuery } from './defaultQueries'; -import { isCloudWatchAnnotationQuery, isCloudWatchLogsQuery, isCloudWatchMetricsQuery } from './guards'; +import { + isCloudWatchAnnotationQuery, + isCloudWatchLogsQuery, + isCloudWatchMetricsQuery, + isLogsAnomaliesQuery, +} from './guards'; import { CloudWatchLogsLanguageProvider } from './language/cloudwatch-logs/CloudWatchLogsLanguageProvider'; import { LogsSQLCompletionItemProvider, @@ -40,6 +45,7 @@ import { ResourcesAPI } from './resources/ResourcesAPI'; import { CloudWatchAnnotationQuery, CloudWatchJsonData, + CloudWatchLogsAnomaliesQuery, CloudWatchLogsQuery, CloudWatchMetricsQuery, CloudWatchQuery, @@ -104,11 +110,14 @@ export class CloudWatchDatasource const logQueries: CloudWatchLogsQuery[] = []; const metricsQueries: CloudWatchMetricsQuery[] = []; + const logsAnomaliesQueries: CloudWatchLogsAnomaliesQuery[] = []; const annotationQueries: CloudWatchAnnotationQuery[] = []; queries.forEach((query) => { if (isCloudWatchAnnotationQuery(query)) { annotationQueries.push(query); + } else if (isLogsAnomaliesQuery(query)) { + logsAnomaliesQueries.push(query); } else if (isCloudWatchLogsQuery(query)) { logQueries.push(query); } else { @@ -127,6 +136,12 @@ export class CloudWatchDatasource ); } + if (logsAnomaliesQueries.length) { + dataQueryResponses.push( + this.logsQueryRunner.handleLogAnomaliesQueries(logsAnomaliesQueries, options, super.query.bind(this)) + ); + } + if (annotationQueries.length) { dataQueryResponses.push( this.annotationQueryRunner.handleAnnotationQuery(annotationQueries, options, super.query.bind(this)) diff --git a/public/app/plugins/datasource/cloudwatch/guards.ts b/public/app/plugins/datasource/cloudwatch/guards.ts index b5346df3c25..2a54dd9719c 100644 --- a/public/app/plugins/datasource/cloudwatch/guards.ts +++ b/public/app/plugins/datasource/cloudwatch/guards.ts @@ -1,10 +1,26 @@ import { AnnotationQuery } from '@grafana/data'; -import { CloudWatchAnnotationQuery, CloudWatchLogsQuery, CloudWatchMetricsQuery, CloudWatchQuery } from './types'; +import { + CloudWatchAnnotationQuery, + CloudWatchLogsAnomaliesQuery, + CloudWatchLogsQuery, + CloudWatchMetricsQuery, + CloudWatchQuery, + LogsMode, +} from './types'; export const isCloudWatchLogsQuery = (cloudwatchQuery: CloudWatchQuery): cloudwatchQuery is CloudWatchLogsQuery => cloudwatchQuery.queryMode === 'Logs'; +export const isLogsAnomaliesQuery = ( + cloudwatchQuery: CloudWatchQuery +): cloudwatchQuery is CloudWatchLogsAnomaliesQuery => { + if (isCloudWatchLogsQuery(cloudwatchQuery)) { + return cloudwatchQuery.logsMode === LogsMode.Anomalies; + } + return false; +}; + export const isCloudWatchMetricsQuery = (cloudwatchQuery: CloudWatchQuery): cloudwatchQuery is CloudWatchMetricsQuery => cloudwatchQuery.queryMode === 'Metrics' || !cloudwatchQuery.hasOwnProperty('queryMode'); // in early versions of this plugin, queryMode wasn't defined in a CloudWatchMetricsQuery diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts index b38fd288633..ec7715dc4df 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.test.ts @@ -2,20 +2,26 @@ import { lastValueFrom, of } from 'rxjs'; import { DataQueryRequest, + DataQueryResponse, + Field, FieldType, LogLevel, LogRowContextQueryDirection, LogRowModel, - MutableDataFrame, } from '@grafana/data'; import { regionVariable } from '../mocks/CloudWatchDataSource'; import { setupMockedLogsQueryRunner } from '../mocks/LogsQueryRunner'; import { LogsRequestMock } from '../mocks/Request'; import { validLogsQuery } from '../mocks/queries'; -import { CloudWatchLogsQuery } from '../types'; // Add this import statement +import { TimeRangeMock } from '../mocks/timeRange'; +import { CloudWatchLogsAnomaliesQuery, CloudWatchLogsQuery, LogsMode } from '../types'; // Add this import statement -import { LOGSTREAM_IDENTIFIER_INTERNAL, LOG_IDENTIFIER_INTERNAL } from './CloudWatchLogsQueryRunner'; +import { + LOGSTREAM_IDENTIFIER_INTERNAL, + LOG_IDENTIFIER_INTERNAL, + convertTrendHistogramToSparkline, +} from './CloudWatchLogsQueryRunner'; describe('CloudWatchLogsQueryRunner', () => { beforeEach(() => { @@ -28,14 +34,15 @@ describe('CloudWatchLogsQueryRunner', () => { const row: LogRowModel = { entryFieldIndex: 0, rowIndex: 0, - dataFrame: new MutableDataFrame({ + dataFrame: { refId: 'B', + length: 1, fields: [ - { name: 'ts', type: FieldType.time, values: [1] }, - { name: LOG_IDENTIFIER_INTERNAL, type: FieldType.string, values: ['foo'], labels: {} }, - { name: LOGSTREAM_IDENTIFIER_INTERNAL, type: FieldType.string, values: ['bar'], labels: {} }, + { name: 'ts', type: FieldType.time, values: [1], config: {} }, + { name: LOG_IDENTIFIER_INTERNAL, type: FieldType.string, values: ['foo'], labels: {}, config: {} }, + { name: LOGSTREAM_IDENTIFIER_INTERNAL, type: FieldType.string, values: ['bar'], labels: {}, config: {} }, ], - }), + }, entry: '4', labels: {}, hasAnsi: false, @@ -481,6 +488,120 @@ describe('CloudWatchLogsQueryRunner', () => { }); }); }); + + describe('handleLogAnomaliesQueries', () => { + it('appends -anomalies to the requestId', async () => { + const { runner, queryMock } = setupMockedLogsQueryRunner(); + const logsAnomaliesRequestMock: DataQueryRequest = { + requestId: 'mockId', + range: TimeRangeMock, + rangeRaw: { from: TimeRangeMock.from, to: TimeRangeMock.to }, + targets: [ + { + id: '1', + logsMode: LogsMode.Anomalies, + queryMode: 'Logs', + refId: 'A', + region: 'us-east-1', + }, + ], + interval: '', + intervalMs: 0, + scopedVars: { __interval: { value: '20s' } }, + timezone: '', + app: '', + startTime: 0, + }; + await expect( + runner.handleLogAnomaliesQueries(LogsRequestMock.targets, logsAnomaliesRequestMock, queryMock) + ).toEmitValuesWith(() => { + expect(queryMock.mock.calls[0][0].requestId).toEqual('mockId-logsAnomalies'); + }); + }); + + it('processes log trend histogram data correctly', async () => { + const response = structuredClone(anomaliesQueryResponse); + + convertTrendHistogramToSparkline(response); + + expect(response.data[0].fields.find((field: Field) => field.name === 'Log trend')).toEqual({ + name: 'Log trend', + type: 'frame', + config: { + custom: { + drawStyle: 'bars', + cellOptions: { + type: 'sparkline', + hideValue: true, + }, + }, + }, + values: [ + { + name: 'Trend_row_0', + length: 8, + fields: [ + { + name: 'time', + type: 'time', + values: [ + 1760454000000, 1760544000000, 1760724000000, 1761282000000, 1761300000000, 1761354000000, + 1761372000000, 1761390000000, + ], + config: {}, + }, + { + name: 'value', + type: 'number', + values: [81, 35, 35, 36, 36, 36, 72, 36], + config: {}, + }, + ], + }, + { + name: 'Trend_row_1', + length: 2, + fields: [ + { + name: 'time', + type: 'time', + values: [1760687665000, 1760687670000], + config: {}, + }, + { + name: 'value', + type: 'number', + values: [3, 3], + config: {}, + }, + ], + }, + ], + }); + }); + + it('replaces log trend histogram field at the same index in the frame', () => { + const response = structuredClone(anomaliesQueryResponse); + convertTrendHistogramToSparkline(response); + expect(response.data[0].fields[4].name).toEqual('Log trend'); + }); + + it('ignore invalid timestamps in log trend histogram', () => { + const response = structuredClone(anomaliesQueryResponse); + + response.data[0].fields[4].values[1] = { + invalidTimestamp: 3, + '1760687670000': 3, + anotherInvalidTimestamp: 2, + '1760687670010': 3, + }; + + convertTrendHistogramToSparkline(response); + + expect(response.data[0].fields[4].values[1].fields[0].values.length).toEqual(2); + expect(response.data[0].fields[4].values[1].fields[1].values.length).toEqual(2); + }); + }); }); const rawLogQueriesStub: CloudWatchLogsQuery[] = [ @@ -641,3 +762,166 @@ const getQueryErrorResponseStub = { const stopQueryResponseStub = { state: 'Done', }; + +const anomaliesQueryResponse: DataQueryResponse = { + data: [ + { + name: 'Logs anomalies', + refId: 'A', + meta: { + preferredVisualisationType: 'table', + }, + fields: [ + { + name: 'state', + type: 'string', + typeInfo: { + frame: 'string', + }, + config: { + displayName: 'State', + }, + values: ['Active', 'Active'], + entities: {}, + }, + { + name: 'description', + type: 'string', + typeInfo: { + frame: 'string', + }, + config: { + displayName: 'Anomaly', + }, + values: [ + '50.0% increase in count of value "405" for "code"-3', + '151.3% increase in count of value 1 for "dotnet_collection_count_total"-3', + ], + entities: {}, + }, + { + name: 'priority', + type: 'string', + typeInfo: { + frame: 'string', + }, + config: { + displayName: 'Priority', + }, + values: ['MEDIUM', 'MEDIUM'], + entities: {}, + }, + { + name: 'patternString', + type: 'string', + typeInfo: { + frame: 'string', + }, + config: { + displayName: 'Log Pattern', + }, + values: [ + '{"ClusterName":"PetSite","Namespace":"default","Service":"service-petsite","Timestamp":<*>,"Version":<*>,"code":<*>,"container_name":"petsite","http_requests_received_total":<*>,"instance":<*>:<*>,"job":"kubernetes-service-endpoints","kubernetes_node":<*>,"method":<*>,"pod_name":<*>,"prom_metric_type":"counter"}', + '{"ClusterName":"PetSite","Namespace":"default","Service":"service-petsite","Timestamp":<*>,"Version":<*>,"container_name":"petsite","dotnet_collection_count_total":<*>,"generation":<*>,"instance":<*>:<*>,"job":"kubernetes-service-endpoints","kubernetes_node":<*>,"pod_name":<*>,"prom_metric_type":"counter"}', + ], + entities: {}, + }, + { + name: 'logTrend', + type: 'other', + typeInfo: { + frame: 'json.RawMessage', + nullable: true, + }, + config: { + displayName: 'Log Trend', + }, + values: [ + { + '1760454000000': 81, + '1760544000000': 35, + '1760724000000': 35, + '1761282000000': 36, + '1761300000000': 36, + '1761354000000': 36, + '1761372000000': 72, + '1761390000000': 36, + }, + { + '1760687665000': 3, + '1760687670000': 3, + }, + ], + entities: {}, + }, + { + name: 'firstSeen', + type: 'time', + typeInfo: { + frame: 'time.Time', + }, + config: { + displayName: 'First seen', + }, + values: [1760462460000, 1760687640000], + entities: {}, + }, + { + name: 'lastSeen', + type: 'time', + typeInfo: { + frame: 'time.Time', + }, + config: { + displayName: 'Last seen', + }, + values: [1761393660000, 1760687940000], + entities: {}, + }, + { + name: 'suppressed', + type: 'boolean', + typeInfo: { + frame: 'bool', + }, + config: { + displayName: 'Suppressed?', + }, + values: [false, false], + entities: {}, + }, + { + name: 'logGroupArnList', + type: 'string', + typeInfo: { + frame: 'string', + }, + config: { + displayName: 'Log Groups', + }, + values: [ + 'arn:aws:logs:us-east-2:569069006612:log-group:/aws/containerinsights/PetSite/prometheus', + 'arn:aws:logs:us-east-2:569069006612:log-group:/aws/containerinsights/PetSite/prometheus', + ], + entities: {}, + }, + { + name: 'anomalyArn', + type: 'string', + typeInfo: { + frame: 'string', + }, + config: { + displayName: 'Anomaly Arn', + }, + values: [ + 'arn:aws:logs:us-east-2:569069006612:anomaly-detector:dca8b129-d09d-4167-86e9-7bf62ede2f95', + 'arn:aws:logs:us-east-2:569069006612:anomaly-detector:dca8b129-d09d-4167-86e9-7bf62ede2f95', + ], + entities: {}, + }, + ], + length: 2, + }, + ], +}; diff --git a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts index 0ccc22029c1..7f1ba927f47 100644 --- a/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts +++ b/public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts @@ -23,6 +23,8 @@ import { DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings, + Field, + FieldType, LoadingState, LogRowContextOptions, LogRowContextQueryDirection, @@ -33,15 +35,19 @@ import { } from '@grafana/data'; import { TemplateSrv } from '@grafana/runtime'; import { type CustomFormatterVariable } from '@grafana/scenes'; +import { GraphDrawStyle } from '@grafana/schema/dist/esm/index'; +import { TableCellDisplayMode } from '@grafana/ui'; import { CloudWatchJsonData, + CloudWatchLogsAnomaliesQuery, CloudWatchLogsQuery, CloudWatchLogsQueryStatus, CloudWatchLogsRequest, CloudWatchQuery, GetLogEventsRequest, LogAction, + LogsMode, LogsQueryLanguage, QueryParam, StartQueryRequest, @@ -101,6 +107,7 @@ export class CloudWatchLogsQueryRunner extends CloudWatchRequest { logGroups, logGroupNames, queryLanguage: target.queryLanguage, + logsMode: target.logsMode ?? LogsMode.Insights, }; }); @@ -136,6 +143,63 @@ export class CloudWatchLogsQueryRunner extends CloudWatchRequest { ); }; + public handleLogAnomaliesQueries = ( + logAnomaliesQueries: CloudWatchLogsAnomaliesQuery[], + options: DataQueryRequest, + queryFn: (request: DataQueryRequest) => Observable + ): Observable => { + const logAnomalyTargets: StartQueryRequest[] = logAnomaliesQueries.map((target: CloudWatchLogsAnomaliesQuery) => { + return { + refId: target.refId, + region: this.templateSrv.replace(this.getActualRegion(target.region)), + queryString: '', + logGroups: [], + logsMode: LogsMode.Anomalies, + suppressionState: target.suppressionState || 'all', + anomalyDetectionARN: target.anomalyDetectionARN || '', + }; + }); + + const range = options?.range || getDefaultTimeRange(); + // append -logsAnomalies to prevent requestId from matching metric or logs queries from the same panel + const requestId = options?.requestId ? `${options?.requestId}-logsAnomalies` : ''; + + const requestParams: DataQueryRequest = { + ...options, + range, + skipQueryCache: true, + requestId, + interval: options?.interval || '', // dummy + intervalMs: options?.intervalMs || 1, // dummy + scopedVars: options?.scopedVars || {}, // dummy + timezone: options?.timezone || '', // dummy + app: options?.app || '', // dummy + startTime: options?.startTime || 0, // dummy + targets: logAnomalyTargets.map((t) => ({ + ...t, + id: '', + queryMode: 'Logs', + refId: t.refId || 'A', + intervalMs: 1, // dummy + maxDataPoints: 1, // dummy + datasource: this.ref, + type: 'logAction', + logsMode: LogsMode.Anomalies, + })), + }; + + return queryFn(requestParams).pipe( + mergeMap((dataQueryResponse) => { + return from( + (async () => { + convertTrendHistogramToSparkline(dataQueryResponse); + return dataQueryResponse; + })() + ); + }) + ); + }; + /** * Called by datasource.ts, invoked when user clicks on a log row in the logs visualization and the "show context button" */ @@ -450,7 +514,7 @@ export class CloudWatchLogsQueryRunner extends CloudWatchRequest { const hasMissingLogGroups = !query.logGroups?.length; const hasMissingQueryString = !query.expression?.length; - // log groups are not mandatory if language is SQL + // log groups are not mandatory if language is SQL and LogsMode is not Insights const isInvalidCWLIQuery = query.queryLanguage !== 'SQL' && hasMissingLogGroups && hasMissingLegacyLogGroupNames; if (isInvalidCWLIQuery || hasMissingQueryString) { return false; @@ -479,3 +543,69 @@ function parseLogGroupName(logIdentifier: string): string { const colonIndex = logIdentifier.lastIndexOf(':'); return logIdentifier.slice(colonIndex + 1); } + +const LOG_TREND_FIELD_NAME = 'logTrend'; + +/** + * Takes DataQueryResponse and converts any "log trend" fields (that are in JSON.rawMessage form) + * into data frame fields that the table vis will be able to display + */ +export function convertTrendHistogramToSparkline(dataQueryResponse: DataQueryResponse): void { + dataQueryResponse.data.forEach((frame) => { + let fieldIndexToReplace = null; + // log trend histogram field from CW API is of shape Record + const sparklineRawData: Field> = frame.fields.find((field: Field, index: number) => { + if (field.name === LOG_TREND_FIELD_NAME && field.type === FieldType.other) { + fieldIndexToReplace = index; + return true; + } + return false; + }); + + if (sparklineRawData) { + const sparklineField: Field = { + name: 'Log trend', + type: FieldType.frame, + config: { + custom: { + drawStyle: GraphDrawStyle.Bars, + cellOptions: { + type: TableCellDisplayMode.Sparkline, + // hiding the value here as it's not useful or clear on what it represents for log trend + hideValue: true, + }, + }, + }, + values: [], + }; + + sparklineRawData.values.forEach((sparklineValue, rowIndex) => { + const timestamps: number[] = []; + const values: number[] = []; + Object.keys(sparklineValue).map((t, i) => { + let n = Number(t); + if (!isNaN(n)) { + timestamps.push(n); + values.push(sparklineValue[t]); + } + }); + + const sparklineFieldFrame: DataFrame = { + name: `Trend_row_${rowIndex}`, + length: timestamps.length, + fields: [ + { name: 'time', type: FieldType.time, values: timestamps, config: {} }, + { name: 'value', type: FieldType.number, values, config: {} }, + ], + }; + + sparklineField.values.push(sparklineFieldFrame); + }); + + if (fieldIndexToReplace) { + // Make sure sparkline field is placed in the same order as coming from BE + frame.fields[fieldIndexToReplace] = sparklineField; + } + } + }); +} diff --git a/public/app/plugins/datasource/cloudwatch/types.ts b/public/app/plugins/datasource/cloudwatch/types.ts index 97db6242aaf..b774bfade40 100644 --- a/public/app/plugins/datasource/cloudwatch/types.ts +++ b/public/app/plugins/datasource/cloudwatch/types.ts @@ -10,6 +10,7 @@ export type CloudWatchQuery = | raw.CloudWatchMetricsQuery | raw.CloudWatchLogsQuery | raw.CloudWatchAnnotationQuery + | raw.CloudWatchLogsAnomaliesQuery | CloudWatchDefaultQuery; // We want to allow setting defaults for both Logs and Metrics queries From f2404361bf53cb0974e79e0a5bc995762497ce4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Oct 2025 18:51:40 +0000 Subject: [PATCH 27/72] deps(actions): bump actions/download-artifact from 5 to 6 (#113024) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/backport-workflow.yml | 2 +- .../detect-breaking-changes-levitate.yml | 6 +++--- .github/workflows/pr-e2e-tests.yml | 16 ++++++++-------- .github/workflows/publish-artifact.yml | 2 +- .github/workflows/release-build.yml | 12 ++++++------ 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/backport-workflow.yml b/.github/workflows/backport-workflow.yml index f3d6761440c..ef7254ffe6a 100644 --- a/.github/workflows/backport-workflow.yml +++ b/.github/workflows/backport-workflow.yml @@ -36,7 +36,7 @@ jobs: private_key: ${{ fromJSON(steps.secrets.outputs.secrets).APP_PEM }} - name: Download PR info artifact - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 id: download-pr-info with: github-token: ${{ github.token }} diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index 2e9b370d4e0..cd2c123e472 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -141,12 +141,12 @@ jobs: node-version-file: '.nvmrc' - name: Get built packages from pr - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: buildPr - name: Get built packages from base - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: buildBase @@ -225,7 +225,7 @@ jobs: persist-credentials: false - name: 'Download artifact' - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: levitate diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index 08b5ad7aba9..30bfc4705ad 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -159,7 +159,7 @@ jobs: with: registry: 'us-docker.pkg.dev' environment: 'dev' - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: grafana-docker-tar-gz path: . @@ -221,10 +221,10 @@ jobs: - uses: actions/checkout@v5 with: persist-credentials: false - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: grafana-tar-gz - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: ${{ needs.build-e2e-runner.outputs.artifact }} - name: chmod +x @@ -298,7 +298,7 @@ jobs: - uses: actions/checkout@v5 with: persist-credentials: false - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: grafana-tar-gz - name: Run E2E tests @@ -360,7 +360,7 @@ jobs: run: | docker cp cpp-e2e-deploy:/outputs.json /tmp/outputs.json - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: grafana-tar-gz @@ -400,7 +400,7 @@ jobs: node-version-file: '.nvmrc' - name: Download blob reports from GitHub Actions Artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: path: blobs pattern: playwright-blob-* @@ -479,7 +479,7 @@ jobs: - uses: actions/checkout@v5 with: persist-credentials: false - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: grafana-tar-gz - name: Run PR a11y test @@ -531,7 +531,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Get pa11y results - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: pa11y-ci-results - name: Extract and publish metrics diff --git a/.github/workflows/publish-artifact.yml b/.github/workflows/publish-artifact.yml index 8ea0b9b90a0..ad0cc86de66 100644 --- a/.github/workflows/publish-artifact.yml +++ b/.github/workflows/publish-artifact.yml @@ -44,7 +44,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: ${{ inputs.name }} pattern: ${{ inputs.pattern }} diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 2fc23359a7a..435fc8b6849 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -224,27 +224,27 @@ jobs: - build steps: - uses: grafana/shared-workflows/actions/dockerhub-login@dockerhub-login/v1.0.2 - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-list-linux-amd64 path: . - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-list-linux-arm64 path: . - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-list-linux-armv7 path: . - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-linux-amd64 path: dist - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-linux-arm64 path: dist - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-linux-armv7 path: dist From a4df6c8bb99ef810bb201ce80b976120a23c102e Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Wed, 29 Oct 2025 16:30:38 -0400 Subject: [PATCH 28/72] Alerting: Prohibit receivers with empty name (#113064) --- pkg/services/ngalert/models/receivers.go | 3 + .../ngalert/notifier/receiver_svc_test.go | 16 ++ .../ngalert/provisioning/contactpoints.go | 21 ++- .../provisioning/contactpoints_test.go | 145 +++++++++++------- 4 files changed, 121 insertions(+), 64 deletions(-) diff --git a/pkg/services/ngalert/models/receivers.go b/pkg/services/ngalert/models/receivers.go index 1130711da90..1601ddc94e5 100644 --- a/pkg/services/ngalert/models/receivers.go +++ b/pkg/services/ngalert/models/receivers.go @@ -130,6 +130,9 @@ func (r *Receiver) WithExistingSecureFields(existing *Receiver, integrationSecur // Validate validates all integration settings, ensuring that the integrations are correctly configured. func (r *Receiver) Validate(decryptFn DecryptFn) error { var errs []error + if r.Name == "" { + errs = append(errs, fmt.Errorf("name should not be an empty string")) + } for _, integration := range r.Integrations { if err := integration.Validate(decryptFn); err != nil { errs = append(errs, err) diff --git a/pkg/services/ngalert/notifier/receiver_svc_test.go b/pkg/services/ngalert/notifier/receiver_svc_test.go index 85e7733f4bd..221f3e805d0 100644 --- a/pkg/services/ngalert/notifier/receiver_svc_test.go +++ b/pkg/services/ngalert/notifier/receiver_svc_test.go @@ -476,6 +476,12 @@ func TestReceiverService_Create(t *testing.T) { }, }, }, + { + name: "receiver with empty name fails", + user: writer, + receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithName("")), + expectedErr: legacy_storage.ErrReceiverInvalid, + }, } { t.Run(tc.name, func(t *testing.T) { sut := createReceiverServiceSut(t, &secretsService) @@ -901,6 +907,16 @@ func TestReceiverService_UpdateReceiverName(t *testing.T) { assert.NotNil(t, ruleStore.Calls[0].Args[4]) assert.Falsef(t, ruleStore.Calls[0].Args[5].(bool), "dryrun expected to be false") }) + + t.Run("returns ErrReceiverInvalid if empty name", func(t *testing.T) { + ruleStore := &fakeAlertRuleNotificationStore{} + sut := createReceiverServiceSut(t, &secretsService) + sut.ruleNotificationsStore = ruleStore + baseReceiver.Name = "" + + _, err := sut.UpdateReceiver(context.Background(), &baseReceiver, nil, writer.GetOrgID(), writer) + require.ErrorIs(t, err, legacy_storage.ErrReceiverInvalid) + }) } func TestReceiverServiceAC_Read(t *testing.T) { diff --git a/pkg/services/ngalert/provisioning/contactpoints.go b/pkg/services/ngalert/provisioning/contactpoints.go index d25aa4e959f..14abb8ceaa5 100644 --- a/pkg/services/ngalert/provisioning/contactpoints.go +++ b/pkg/services/ngalert/provisioning/contactpoints.go @@ -309,13 +309,14 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in return err } - oldReceiverName, fullRemoval, newReceiverCreated := stitchReceiver(revision.Config, mergedReceiver) - if oldReceiverName == "" { - return fmt.Errorf("contact point with uid '%s' not found", mergedReceiver.UID) + oldReceiverNameRef, fullRemoval, newReceiverCreated := stitchReceiver(revision.Config, mergedReceiver) + if oldReceiverNameRef == nil { + return fmt.Errorf("%w: contact point with uid '%s' not found", ErrNotFound, mergedReceiver.UID) } + oldReceiverName := *oldReceiverNameRef err = ecp.xact.InTransaction(ctx, func(ctx context.Context) error { - if mergedReceiver.Name != oldReceiverName { + if mergedReceiver.Name != oldReceiverName && oldReceiverName != "" { if newReceiverCreated { // Copy receiver permissions permissionsUpdated, err := ecp.resourcePermissions.CopyPermissions(ctx, orgID, nil, legacy_storage.NameToUid(oldReceiverName), legacy_storage.NameToUid(mergedReceiver.Name)) @@ -373,12 +374,12 @@ func (ecp *ContactPointService) DeleteContactPoint(ctx context.Context, orgID in } } } - if fullRemoval && revision.ReceiverNameUsedByRoutes(name) { + if fullRemoval && name != "" && revision.ReceiverNameUsedByRoutes(name) { return ErrContactPointReferenced.Errorf("") } return ecp.xact.InTransaction(ctx, func(ctx context.Context) error { - if fullRemoval { + if fullRemoval && name != "" { used, err := ecp.notificationSettingsStore.ListNotificationSettings(ctx, models.ListNotificationSettingsQuery{OrgID: orgID, ReceiverName: name}) if err != nil { return fmt.Errorf("failed to query alert rules for reference to the contact point '%s': %w", name, err) @@ -443,7 +444,7 @@ func (ecp *ContactPointService) encryptValue(value string) (string, error) { // stitchReceiver modifies a receiver, target, in an alertmanager configStore. It modifies the given configStore in-place. // Returns true if the configStore was altered in any way, and false otherwise. // If integration was moved to another group and it was the last in the previous group, the second parameter contains the name of the old group that is gone -func stitchReceiver(cfg *apimodels.PostableUserConfig, target *apimodels.PostableGrafanaReceiver) (oldReceiverName string, fullRemoval bool, newReceiverCreated bool) { +func stitchReceiver(cfg *apimodels.PostableUserConfig, target *apimodels.PostableGrafanaReceiver) (oldReceiverName *string, fullRemoval bool, newReceiverCreated bool) { // Algorithm to fix up receivers. Receivers are very complex and depend heavily on internal consistency. // All receivers in a given receiver group have the same name. We must maintain this across renames. groupLoop: @@ -451,7 +452,8 @@ groupLoop: // Does the current group contain the grafana receiver we're interested in? for i, grafanaReceiver := range receiverGroup.GrafanaManagedReceivers { if grafanaReceiver.UID == target.UID { - oldReceiverName = receiverGroup.Name + name := receiverGroup.Name + oldReceiverName = &name // If it's a basic field change, simply replace it. Done! // // NOTE: @@ -519,6 +521,9 @@ groupLoop: } func ValidateContactPoint(ctx context.Context, e *apimodels.EmbeddedContactPoint, decryptFunc alertingNotify.GetDecryptedValueFn) error { + if e.Name == "" { + return errors.New("name is required") + } iType, err := alertingNotify.IntegrationTypeFromString(e.Type) if err != nil { return err diff --git a/pkg/services/ngalert/provisioning/contactpoints_test.go b/pkg/services/ngalert/provisioning/contactpoints_test.go index 34535c1a504..ddddadb1104 100644 --- a/pkg/services/ngalert/provisioning/contactpoints_test.go +++ b/pkg/services/ngalert/provisioning/contactpoints_test.go @@ -114,16 +114,6 @@ func TestIntegrationContactPointService(t *testing.T) { require.Equal(t, customUID, cps[0].UID) }) - t.Run("it's not possible to use invalid UID", func(t *testing.T) { - customUID := strings.Repeat("1", util.MaxUIDLength+1) - sut := createContactPointServiceSut(t, secretsService) - newCp := createTestContactPoint() - newCp.UID = customUID - - _, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) - require.ErrorIs(t, err, ErrValidation) - }) - t.Run("it's not possible to use the same uid twice", func(t *testing.T) { customUID := "1337" sut := createContactPointServiceSut(t, secretsService) @@ -137,14 +127,40 @@ func TestIntegrationContactPointService(t *testing.T) { require.Error(t, err) }) - t.Run("create rejects contact points that fail validation", func(t *testing.T) { + t.Run("create rejects invalid contact points", func(t *testing.T) { sut := createContactPointServiceSut(t, secretsService) - newCp := createTestContactPoint() - newCp.Type = "" + testCases := []struct { + name string + cp func(*definitions.EmbeddedContactPoint) + }{ + { + name: "empty type", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Type = "" + }, + }, + { + name: "empty name", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Name = "" + }, + }, + { + name: "invalid UID", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.UID = strings.Repeat("1", util.MaxUIDLength+1) + }, + }, + } - _, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) - - require.ErrorIs(t, err, ErrValidation) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + newCp := createTestContactPoint() + tc.cp(&newCp) + _, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) + require.ErrorIs(t, err, ErrValidation) + }) + } }) t.Run("create accepts contact point with type in different cases", func(t *testing.T) { @@ -162,40 +178,57 @@ func TestIntegrationContactPointService(t *testing.T) { assert.EqualValues(t, slack.Type, got[0].Type) }) - t.Run("update rejects contact points with no settings", func(t *testing.T) { + t.Run("update rejects invalid contact points", func(t *testing.T) { + testCases := []struct { + name string + cp func(*definitions.EmbeddedContactPoint) + }{ + { + name: "empty type", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Type = "" + }, + }, + { + name: "empty name", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Name = "" + }, + }, + { + name: "nil settings", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Settings = nil + }, + }, + { + name: "invalid settings after merge", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Settings, _ = simplejson.NewJson([]byte(`{}`)) + }, + }, + } + sut := createContactPointServiceSut(t, secretsService) newCp := createTestContactPoint() newCp, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) require.NoError(t, err) - newCp.Settings = nil - err = sut.UpdateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI) - - require.ErrorIs(t, err, ErrValidation) - }) - - t.Run("update rejects contact points with no type", func(t *testing.T) { - sut := createContactPointServiceSut(t, secretsService) - newCp := createTestContactPoint() - newCp, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) - require.NoError(t, err) - newCp.Type = "" - - err = sut.UpdateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI) - - require.ErrorIs(t, err, ErrValidation) - }) - - t.Run("update rejects contact points which fail validation after merging", func(t *testing.T) { - sut := createContactPointServiceSut(t, secretsService) - newCp := createTestContactPoint() - newCp, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) - require.NoError(t, err) - newCp.Settings, _ = simplejson.NewJson([]byte(`{}`)) - - err = sut.UpdateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI) - - require.ErrorIs(t, err, ErrValidation) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cp := definitions.EmbeddedContactPoint{ + UID: newCp.UID, + Name: newCp.Name, + Type: newCp.Type, + Settings: newCp.Settings.DeepCopy(), + DisableResolveMessage: newCp.DisableResolveMessage, + Provenance: newCp.Provenance, + } + tc.cp(&cp) + err = sut.UpdateContactPoint(context.Background(), 1, cp, models.ProvenanceAPI) + require.ErrorIs(t, err, ErrValidation) + }) + } }) t.Run("update accepts contact points with type in another case", func(t *testing.T) { @@ -592,7 +625,7 @@ func TestStitchReceivers(t *testing.T) { initial *definitions.PostableUserConfig new *definitions.PostableGrafanaReceiver expCfg definitions.PostableApiAlertingConfig - expOldReceiver string + expOldReceiver *string expCreatedReceiver bool expFullRemoval bool } @@ -603,7 +636,7 @@ func TestStitchReceivers(t *testing.T) { new: &definitions.PostableGrafanaReceiver{ UID: "does not exist", }, - expOldReceiver: "", + expOldReceiver: nil, expCfg: createTestConfigWithReceivers().AlertmanagerConfig, }, { @@ -613,7 +646,7 @@ func TestStitchReceivers(t *testing.T) { Name: "receiver-2", Type: "teams", }, - expOldReceiver: "receiver-2", + expOldReceiver: util.Pointer("receiver-2"), expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ Route: &definitions.Route{ @@ -674,7 +707,7 @@ func TestStitchReceivers(t *testing.T) { Name: "new-receiver", Type: "slack", }, - expOldReceiver: "receiver-1", + expOldReceiver: util.Pointer("receiver-1"), expCreatedReceiver: true, expFullRemoval: true, expCfg: definitions.PostableApiAlertingConfig{ @@ -737,7 +770,7 @@ func TestStitchReceivers(t *testing.T) { Name: "receiver-1", Type: "slack", }, - expOldReceiver: "receiver-2", + expOldReceiver: util.Pointer("receiver-2"), expCreatedReceiver: false, expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ @@ -858,7 +891,7 @@ func TestStitchReceivers(t *testing.T) { Name: "receiver-2", Type: "slack", }, - expOldReceiver: "receiver-1", + expOldReceiver: util.Pointer("receiver-1"), expCreatedReceiver: false, expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ @@ -1002,7 +1035,7 @@ func TestStitchReceivers(t *testing.T) { Name: "receiver-4", Type: "slack", }, - expOldReceiver: "receiver-1", + expOldReceiver: util.Pointer("receiver-1"), expCreatedReceiver: false, expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ @@ -1087,7 +1120,7 @@ func TestStitchReceivers(t *testing.T) { Name: "brand-new-group", Type: "opsgenie", }, - expOldReceiver: "receiver-2", + expOldReceiver: util.Pointer("receiver-2"), expCreatedReceiver: true, expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ @@ -1159,7 +1192,7 @@ func TestStitchReceivers(t *testing.T) { Name: "brand-new-group", Type: "opsgenie", }, - expOldReceiver: "receiver-2", // Not the inconsistent receiver-3? + expOldReceiver: util.Pointer("receiver-2"), // Not the inconsistent receiver-3? expCreatedReceiver: true, expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ @@ -1282,7 +1315,7 @@ func TestStitchReceivers(t *testing.T) { Name: "receiver-1", Type: "slack", }, - expOldReceiver: "receiver-2", + expOldReceiver: util.Pointer("receiver-2"), expCreatedReceiver: false, expFullRemoval: true, expCfg: definitions.PostableApiAlertingConfig{ @@ -1337,7 +1370,7 @@ func TestStitchReceivers(t *testing.T) { } renamedReceiver, fullRemoval, createdReceiver := stitchReceiver(cfg, c.new) - assert.Equalf(t, c.expOldReceiver, renamedReceiver, "expected old receiver to be %s, got %s", c.expOldReceiver, renamedReceiver) + assert.EqualValuesf(t, c.expOldReceiver, renamedReceiver, "expected old receiver to be %v, got %v", c.expOldReceiver, renamedReceiver) assert.Equalf(t, c.expFullRemoval, fullRemoval, "expected full removal to be %t, got %t", c.expFullRemoval, fullRemoval) assert.Equalf(t, c.expCreatedReceiver, createdReceiver, "expected created receiver to be %t, got %t", c.expCreatedReceiver, createdReceiver) require.Equal(t, c.expCfg, cfg.AlertmanagerConfig) From c0c31afdde9b327d7c96ca1205d49f5fa0d620f1 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 00:40:00 +0000 Subject: [PATCH 29/72] I18n: Download translations from Crowdin (#113191) 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 | 31 ++++++++++++++++++++++++++--- public/locales/de-DE/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/es-ES/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/fr-FR/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/hu-HU/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/id-ID/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/it-IT/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/ja-JP/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/ko-KR/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/nl-NL/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/pl-PL/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/pt-BR/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/pt-PT/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/ru-RU/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/sv-SE/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/tr-TR/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/zh-Hans/grafana.json | 31 ++++++++++++++++++++++++++--- public/locales/zh-Hant/grafana.json | 31 ++++++++++++++++++++++++++--- 18 files changed, 504 insertions(+), 54 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 9abd78940c1..2a174ff7420 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -4197,6 +4197,7 @@ "cancel": "Zrušit", "clear": "Vymazat", "collapse": "Sbalit", + "disabled": "", "edit": "Upravit", "help": "Nápověda", "loading": "Načítání…", @@ -5175,6 +5176,19 @@ "aria-label-remove-override": "Odebrat přepsání", "tooltip-remove-override": "Odebrat přepsání" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Před přidáním výstrahy musí být nástěnka uložena.", @@ -5730,6 +5744,11 @@ "open-in-new-tab": "Otevřít v nové záložce", "title-error-adding-the-panel": "Chyba při přidávání panelu" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Zpět na seznam", "delete": "Odstranit", @@ -7694,7 +7713,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Nebyly nalezeny žádné složky", "select-aria-label": "Filtr složky", "select-placeholder": "Filtrovat podle složky" }, @@ -10964,6 +10982,7 @@ "replace-library-panel": "Vyměnit panel knihovny", "share": "Sdílet", "show-legend": "Zobrazit legendu", + "time-settings": "", "unlink-library-panel": "Odpojit panel knihovny", "view": "Zobrazit" }, @@ -12400,8 +12419,6 @@ "title": "Vyberte rozsahy" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Nebyly nalezeny žádné výsledky pro váš dotaz", "recommended": "Doporučeno", @@ -13249,6 +13266,14 @@ "title": "Panel" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Naposledy použité absolutní rozsahy", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 6aa5ee76432..b57ca0032d4 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -4157,6 +4157,7 @@ "cancel": "Abbrechen", "clear": "Löschen", "collapse": "Einklappen", + "disabled": "", "edit": "Bearbeiten", "help": "Hilfe", "loading": "Wird geladen ...", @@ -5133,6 +5134,19 @@ "aria-label-remove-override": "Überschreibung entfernen", "tooltip-remove-override": "Überschreibung entfernen" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Das Dashboard muss gespeichert werden, bevor Warnungen hinzugefügt werden können.", @@ -5688,6 +5702,11 @@ "open-in-new-tab": "In neuem Tab öffnen", "title-error-adding-the-panel": "Fehler beim Hinzufügen des Panels" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Zurück zur Liste", "delete": "Löschen", @@ -7644,7 +7663,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Keine Ordner gefunden", "select-aria-label": "Ordnerfilter", "select-placeholder": "Nach Ordner filtern" }, @@ -10872,6 +10890,7 @@ "replace-library-panel": "Bibliotheks-Panel ersetzen", "share": "Teilen", "show-legend": "Legende anzeigen", + "time-settings": "", "unlink-library-panel": "Verknüpfung mit der Bibliotheksleiste aufheben", "view": "Anzeigen" }, @@ -12290,8 +12309,6 @@ "title": "Bereiche auswählen" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Keine Ergebnisse für deine Abfrage gefunden", "recommended": "Empfohlen", @@ -13135,6 +13152,14 @@ "title": "Panel" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Kürzlich verwendete absolute Bereiche", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 6e93bc641b8..6503fc1080e 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -4157,6 +4157,7 @@ "cancel": "Cancelar", "clear": "Borrar", "collapse": "Contraer", + "disabled": "", "edit": "Editar", "help": "Ayuda", "loading": "Cargando...", @@ -5133,6 +5134,19 @@ "aria-label-remove-override": "Quitar anulación", "tooltip-remove-override": "Quitar anulación" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "El panel de control debe guardarse antes de poder añadir alertas.", @@ -5688,6 +5702,11 @@ "open-in-new-tab": "Abrir en pestaña nueva", "title-error-adding-the-panel": "Error al añadir el panel" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Regresar a la lista", "delete": "Eliminar", @@ -7644,7 +7663,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "No se han encontrado carpetas", "select-aria-label": "Filtro de carpeta", "select-placeholder": "Filtrar por carpeta" }, @@ -10872,6 +10890,7 @@ "replace-library-panel": "Reemplazar panel de la biblioteca", "share": "Compartir", "show-legend": "Mostrar leyenda", + "time-settings": "", "unlink-library-panel": "Desvincular panel de librería", "view": "Vista" }, @@ -12290,8 +12309,6 @@ "title": "Seleccionar ámbitos" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "No se han encontrado resultados para tu consulta", "recommended": "Recomendado", @@ -13135,6 +13152,14 @@ "title": "Panel" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Intervalos absolutos utilizados recientemente", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 5f59f4eb023..7acded43c93 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -4157,6 +4157,7 @@ "cancel": "Annuler", "clear": "Effacer", "collapse": "Réduire", + "disabled": "", "edit": "Modifier", "help": "Aide", "loading": "Chargement en cours...", @@ -5133,6 +5134,19 @@ "aria-label-remove-override": "Retirer le remplacement", "tooltip-remove-override": "Retirer le remplacement" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Le tableau de bord doit être enregistré avant de pouvoir ajouter des alertes.", @@ -5688,6 +5702,11 @@ "open-in-new-tab": "Ouvrir dans un nouvel onglet", "title-error-adding-the-panel": "Erreur lors de l’ajout du panneau" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Retour à la liste", "delete": "Supprimer", @@ -7644,7 +7663,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Aucun dossier trouvé", "select-aria-label": "Filtre de dossier", "select-placeholder": "Filtrer par dossier" }, @@ -10872,6 +10890,7 @@ "replace-library-panel": "Remplacer le panneau de bibliothèque", "share": "Partager", "show-legend": "Afficher la légende", + "time-settings": "", "unlink-library-panel": "Dissocier le panneau de la bibliothèque", "view": "Afficher" }, @@ -12290,8 +12309,6 @@ "title": "Sélectionner les portées" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Aucun résultat n'a été trouvé pour votre requête", "recommended": "Recommandé", @@ -13135,6 +13152,14 @@ "title": "Panneau" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Périodes absolues récemment utilisées", diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index c18d53a7831..3da3dec91b9 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -4157,6 +4157,7 @@ "cancel": "Mégse", "clear": "Törlés", "collapse": "Összecsukás", + "disabled": "", "edit": "Szerkesztés", "help": "Súgó", "loading": "Betöltés...", @@ -5133,6 +5134,19 @@ "aria-label-remove-override": "Felülbírálás eltávolítása", "tooltip-remove-override": "Felülbírálás eltávolítása" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Riasztások hozzáadása előtt menteni kell az irányítópultot.", @@ -5688,6 +5702,11 @@ "open-in-new-tab": "Megnyitás új lapon", "title-error-adding-the-panel": "Hiba történt a panel hozzáadásakor" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Vissza a listához", "delete": "Törlés", @@ -7644,7 +7663,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Nem található mappa", "select-aria-label": "Mappaszűrő", "select-placeholder": "Szűrés mappa alapján" }, @@ -10872,6 +10890,7 @@ "replace-library-panel": "Könyvtárpanel cseréje", "share": "Megosztás", "show-legend": "Jelmagyarázat megjelenítése", + "time-settings": "", "unlink-library-panel": "Könyvtárpanel leválasztása", "view": "Nézet" }, @@ -12290,8 +12309,6 @@ "title": "Hatókörök kijelölése" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Nincs találat a lekérdezésre", "recommended": "Ajánlott", @@ -13135,6 +13152,14 @@ "title": "Panel" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Legutóbb használt abszolút tartományok", diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 97627226a56..e7be931cea2 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -4137,6 +4137,7 @@ "cancel": "Batalkan", "clear": "Hapus", "collapse": "Ciutkan", + "disabled": "", "edit": "Edit", "help": "Bantuan", "loading": "Memuat...", @@ -5112,6 +5113,19 @@ "aria-label-remove-override": "Hapus penimpaan", "tooltip-remove-override": "Hapus penimpaan" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Dasbor harus disimpan sebelum peringatan dapat ditambahkan.", @@ -5667,6 +5681,11 @@ "open-in-new-tab": "Buka di tab baru", "title-error-adding-the-panel": "Kesalahan saat menambahkan panel" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Kembali ke daftar", "delete": "Hapus", @@ -7619,7 +7638,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Folder tidak ditemukan", "select-aria-label": "Filter folder", "select-placeholder": "Filter berdasarkan folder" }, @@ -10826,6 +10844,7 @@ "replace-library-panel": "Ganti panel pustaka", "share": "Bagikan", "show-legend": "Tampilkan keterangan", + "time-settings": "", "unlink-library-panel": "Putuskan tautan panel pustaka", "view": "Lihat" }, @@ -12235,8 +12254,6 @@ "title": "Pilih cakupan" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Hasil untuk kueri Anda tidak ditemukan", "recommended": "Disarankan", @@ -13078,6 +13095,14 @@ "title": "Panel" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Rentang absolut yang baru digunakan", diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 4b5dc671573..76d6ebd06a5 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -4157,6 +4157,7 @@ "cancel": "Annulla", "clear": "Cancella", "collapse": "Riduci", + "disabled": "", "edit": "Modifica", "help": "Guida", "loading": "Caricamento in corso...", @@ -5133,6 +5134,19 @@ "aria-label-remove-override": "Rimuovi sovrascrittura", "tooltip-remove-override": "Rimuovi sovrascrittura" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Il dashboard deve essere salvato prima di poter aggiungere avvisi.", @@ -5688,6 +5702,11 @@ "open-in-new-tab": "Apri in una nuova scheda", "title-error-adding-the-panel": "Errore durante l'aggiunta del pannello" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Torna all'elenco", "delete": "Elimina", @@ -7644,7 +7663,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Nessuna cartella trovata", "select-aria-label": "Filtro cartelle", "select-placeholder": "Filtra per cartella" }, @@ -10872,6 +10890,7 @@ "replace-library-panel": "Sostituisci il pannello della libreria", "share": "Condividi", "show-legend": "Mostra leggenda", + "time-settings": "", "unlink-library-panel": "Scollega pannello della libreria", "view": "Visualizza" }, @@ -12290,8 +12309,6 @@ "title": "Seleziona gli ambiti" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Nessun risultato trovato per la ricerca", "recommended": "Consigliato", @@ -13135,6 +13152,14 @@ "title": "Pannello" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Intervalli assoluti utilizzati di recente", diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 5b24acb3434..a41d40d75f8 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -4137,6 +4137,7 @@ "cancel": "キャンセル", "clear": "消去", "collapse": "折りたたみ表示", + "disabled": "", "edit": "編集", "help": "ヘルプ", "loading": "読み込み中...", @@ -5112,6 +5113,19 @@ "aria-label-remove-override": "オーバーライドを削除", "tooltip-remove-override": "オーバーライドを削除" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "アラートを追加する前に、ダッシュボードを保存する必要があります。", @@ -5667,6 +5681,11 @@ "open-in-new-tab": "新しいタブで開く", "title-error-adding-the-panel": "パネル追加時のエラー" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "一覧に戻る", "delete": "削除", @@ -7619,7 +7638,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "フォルダが見つかりません", "select-aria-label": "フォルダフィルター", "select-placeholder": "フォルダでフィルタリング" }, @@ -10826,6 +10844,7 @@ "replace-library-panel": "ライブラリパネルを置き換える", "share": "共有", "show-legend": "凡例を表示", + "time-settings": "", "unlink-library-panel": "ライブラリパネルのリンクを解除", "view": "表示" }, @@ -12235,8 +12254,6 @@ "title": "スコープを選択" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "クエリに一致する結果が見つかりませんでした。", "recommended": "おすすめ", @@ -13078,6 +13095,14 @@ "title": "パネル" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "最近使用した絶対的な範囲", diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 469989c8707..1f3be6553d3 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -4137,6 +4137,7 @@ "cancel": "취소", "clear": "초기화", "collapse": "접기", + "disabled": "", "edit": "편집", "help": "도움말", "loading": "로딩 중...", @@ -5112,6 +5113,19 @@ "aria-label-remove-override": "재정의 제거", "tooltip-remove-override": "재정의 제거" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "경고를 추가하기 전에 먼저 대시보드를 저장해야 합니다.", @@ -5667,6 +5681,11 @@ "open-in-new-tab": "새 탭에서 열기", "title-error-adding-the-panel": "패널 추가 중 오류 발생" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "목록으로 돌아가기", "delete": "삭제", @@ -7619,7 +7638,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "폴더를 찾을 수 없습니다", "select-aria-label": "폴더 필터", "select-placeholder": "폴더별로 필터링" }, @@ -10826,6 +10844,7 @@ "replace-library-panel": "라이브러리 패널 교체", "share": "공유", "show-legend": "범례 보기", + "time-settings": "", "unlink-library-panel": "라이브러리 패널 연결 해제", "view": "보기" }, @@ -12235,8 +12254,6 @@ "title": "범위 선택" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "쿼리에 대해 찾은 결과 없음", "recommended": "권장", @@ -13078,6 +13095,14 @@ "title": "패널" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "최근에 사용한 절대 범위", diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 9e46e64fb84..4c263008165 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -4157,6 +4157,7 @@ "cancel": "Annuleren", "clear": "Wissen", "collapse": "Samenvouwen", + "disabled": "", "edit": "Bewerken", "help": "Help", "loading": "Bezig met laden ...", @@ -5133,6 +5134,19 @@ "aria-label-remove-override": "Overschrijving verwijderen", "tooltip-remove-override": "Overschrijving verwijderen" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Dashboard moet worden opgeslagen voordat waarschuwingen kunnen worden toegevoegd.", @@ -5688,6 +5702,11 @@ "open-in-new-tab": "In een nieuw tabblad openen", "title-error-adding-the-panel": "Er is een fout opgetreden bij het toevoegen van het paneel" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Terug naar lijst", "delete": "Verwijderen", @@ -7644,7 +7663,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Geen mappen gevonden", "select-aria-label": "Mapfilter", "select-placeholder": "Filteren op map" }, @@ -10872,6 +10890,7 @@ "replace-library-panel": "Bibliotheekpaneel vervangen", "share": "Delen", "show-legend": "Legenda tonen", + "time-settings": "", "unlink-library-panel": "Bibliotheekpaneel ontkoppelen", "view": "Weergave" }, @@ -12290,8 +12309,6 @@ "title": "Scopes selecteren" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Geen resultaten gevonden voor je zoekopdracht", "recommended": "Aanbevolen", @@ -13135,6 +13152,14 @@ "title": "Paneel" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Recent gebruikte absolute bereiken", diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index ab686289d01..c308ccfeaf8 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -4197,6 +4197,7 @@ "cancel": "Anuluj", "clear": "Wyczyść", "collapse": "Zwiń", + "disabled": "", "edit": "Edytuj", "help": "Pomoc", "loading": "Ładowanie…", @@ -5175,6 +5176,19 @@ "aria-label-remove-override": "Usuń zastąpienie", "tooltip-remove-override": "Usuń zastąpienie" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Przed dodaniem alertów należy zapisać pulpit.", @@ -5730,6 +5744,11 @@ "open-in-new-tab": "Otwórz w nowej karcie", "title-error-adding-the-panel": "Błąd dodawania panelu" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Powrót do listy", "delete": "Usuń", @@ -7694,7 +7713,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Nie znaleziono folderów", "select-aria-label": "Filtr folderów", "select-placeholder": "Filtruj wg folderu" }, @@ -10964,6 +10982,7 @@ "replace-library-panel": "Zastąp panel biblioteki", "share": "Udostępnij", "show-legend": "Pokaż legendę", + "time-settings": "", "unlink-library-panel": "Rozłącz panel biblioteki", "view": "Widok" }, @@ -12400,8 +12419,6 @@ "title": "Wybierz zakresy" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Nie znaleziono wyników dla tego zapytania", "recommended": "Zalecane", @@ -13249,6 +13266,14 @@ "title": "Panel" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Ostatnio używane zakresy bezwzględne", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 9b2d4662786..2287cd91f81 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -4157,6 +4157,7 @@ "cancel": "Cancelar", "clear": "Limpar", "collapse": "Recolher", + "disabled": "", "edit": "Editar", "help": "Ajuda", "loading": "Carregando...", @@ -5133,6 +5134,19 @@ "aria-label-remove-override": "Remover substituição", "tooltip-remove-override": "Remover substituição" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "O painel de controle deve ser salvo para que os alertas possam ser adicionados.", @@ -5688,6 +5702,11 @@ "open-in-new-tab": "Abrir em nova aba", "title-error-adding-the-panel": "Erro ao adicionar o painel" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Voltar para a lista", "delete": "Excluir", @@ -7644,7 +7663,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Nenhuma pasta encontrada", "select-aria-label": "Filtro de pasta", "select-placeholder": "Filtrar por pasta" }, @@ -10872,6 +10890,7 @@ "replace-library-panel": "Substituir painel da biblioteca", "share": "Compartilhar", "show-legend": "Mostrar legenda", + "time-settings": "", "unlink-library-panel": "Desvincular painel de biblioteca", "view": "Visualizar" }, @@ -12290,8 +12309,6 @@ "title": "Selecionar escopos" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Nenhum resultado encontrado para sua consulta", "recommended": "Recomendado", @@ -13135,6 +13152,14 @@ "title": "Painel" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Intervalos absolutos usados recentemente", diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index db0d3d98b1a..48b38a197d7 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -4157,6 +4157,7 @@ "cancel": "Cancelar", "clear": "Limpar", "collapse": "Recolher", + "disabled": "", "edit": "Editar", "help": "Ajuda", "loading": "A carregar...", @@ -5133,6 +5134,19 @@ "aria-label-remove-override": "Remover a substituição", "tooltip-remove-override": "Remover a substituição" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "O painel de controlo deve ser guardado antes de poderem ser adicionados alertas.", @@ -5688,6 +5702,11 @@ "open-in-new-tab": "Abrir num novo separador", "title-error-adding-the-panel": "Erro ao adicionar o painel" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Voltar à lista", "delete": "Eliminar", @@ -7644,7 +7663,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Nenhuma pasta encontrada", "select-aria-label": "Filtro de pastas", "select-placeholder": "Filtrar por pasta" }, @@ -10872,6 +10890,7 @@ "replace-library-panel": "Substituir painel de biblioteca", "share": "Partilhar", "show-legend": "Mostrar legenda", + "time-settings": "", "unlink-library-panel": "Desassociar painel de biblioteca", "view": "Ver" }, @@ -12290,8 +12309,6 @@ "title": "Selecionar âmbitos" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Não foram encontrados resultados para a sua consulta", "recommended": "Recomendado", @@ -13135,6 +13152,14 @@ "title": "Painel" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Intervalos absolutos usados recentemente", diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 51037975989..da5960489da 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -4197,6 +4197,7 @@ "cancel": "Отмена", "clear": "Очистить", "collapse": "Свернуть", + "disabled": "", "edit": "Редактировать", "help": "Справка", "loading": "Загрузка…", @@ -5175,6 +5176,19 @@ "aria-label-remove-override": "Удалить переопределение", "tooltip-remove-override": "Удалить переопределение" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Перед добавлением оповещений необходимо сохранить дашборд.", @@ -5730,6 +5744,11 @@ "open-in-new-tab": "Открыть в новой вкладке", "title-error-adding-the-panel": "Ошибка при добавлении панели" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Назад к списку", "delete": "Удалить", @@ -7694,7 +7713,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Папки не найдены", "select-aria-label": "Фильтр папок", "select-placeholder": "Фильтровать по папкам" }, @@ -10964,6 +10982,7 @@ "replace-library-panel": "Заменить панель библиотеки", "share": "Общий доступ", "show-legend": "Показать условные обозначения", + "time-settings": "", "unlink-library-panel": "Отсоединить панель библиотеки", "view": "Просмотр" }, @@ -12400,8 +12419,6 @@ "title": "Выбор областей" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "По вашему запросу ничего не найдено", "recommended": "Рекомендуемые", @@ -13249,6 +13266,14 @@ "title": "Панель" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Последние использованные абсолютные диапазоны", diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 5ede98266b1..8e96d2aaae8 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -4157,6 +4157,7 @@ "cancel": "Avbryt", "clear": "Rensa", "collapse": "Minimera", + "disabled": "", "edit": "Redigera", "help": "Hjälp", "loading": "Laddar …", @@ -5133,6 +5134,19 @@ "aria-label-remove-override": "Ta bort åsidosättning", "tooltip-remove-override": "Ta bort åsidosättning" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Instrumentpanelen måste sparas innan varningar kan läggas till.", @@ -5688,6 +5702,11 @@ "open-in-new-tab": "Öppna i ny flik", "title-error-adding-the-panel": "Fel vid tillägg av panelen" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Tillbaka till listan", "delete": "Ta bort", @@ -7644,7 +7663,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Inga mappar hittades", "select-aria-label": "Mappfilter", "select-placeholder": "Filtrera efter mapp" }, @@ -10872,6 +10890,7 @@ "replace-library-panel": "Byt ut bibliotekspanel", "share": "Dela", "show-legend": "Visa förklaring", + "time-settings": "", "unlink-library-panel": "Ta bort länk till bibliotekspanel", "view": "Visa" }, @@ -12290,8 +12309,6 @@ "title": "Välj omfattningar" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Inga resultat hittades för din fråga", "recommended": "Rekommenderas", @@ -13135,6 +13152,14 @@ "title": "Panel" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Nyligen använda absoluta intervall", diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 432dfa7979f..e6682536014 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -4157,6 +4157,7 @@ "cancel": "İptal", "clear": "Temizle", "collapse": "Daralt", + "disabled": "", "edit": "Düzenle", "help": "Yardım", "loading": "Yükleniyor...", @@ -5133,6 +5134,19 @@ "aria-label-remove-override": "Geçersiz kılmayı kaldır", "tooltip-remove-override": "Geçersiz kılmayı kaldır" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "Uyarılar eklenmeden önce pano kaydedilmelidir.", @@ -5688,6 +5702,11 @@ "open-in-new-tab": "Yeni sekmede aç", "title-error-adding-the-panel": "Panel eklenirken hata oluştu" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "Listeye geri dön", "delete": "Sil", @@ -7644,7 +7663,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "Klasör bulunamadı", "select-aria-label": "Klasör filtresi", "select-placeholder": "Klasöre göre filtrele" }, @@ -10872,6 +10890,7 @@ "replace-library-panel": "Kütüphane panelini değiştir", "share": "Paylaş", "show-legend": "Açıklamayı göster", + "time-settings": "", "unlink-library-panel": "Kütüphane panelinin bağlantısını kaldır", "view": "Görüntüle" }, @@ -12290,8 +12309,6 @@ "title": "Kapsam seçin" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "Sorgunuz için sonuç bulunamadı", "recommended": "Önerilen", @@ -13135,6 +13152,14 @@ "title": "Panel" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "Son kullanılan mutlak aralıklar", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 339ae6027eb..275f7cc605f 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -4137,6 +4137,7 @@ "cancel": "取消", "clear": "清除", "collapse": "收起", + "disabled": "", "edit": "编辑", "help": "帮助", "loading": "加载中...", @@ -5112,6 +5113,19 @@ "aria-label-remove-override": "移除覆盖", "tooltip-remove-override": "移除覆盖" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "必须先保存数据面板,然后才能添加提醒。", @@ -5667,6 +5681,11 @@ "open-in-new-tab": "在新标签页中打开", "title-error-adding-the-panel": "添加面板时出错" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "回到列表", "delete": "删除", @@ -7619,7 +7638,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "未找到文件夹", "select-aria-label": "文件夹筛选", "select-placeholder": "按文件夹筛选" }, @@ -10826,6 +10844,7 @@ "replace-library-panel": "替换库面板", "share": "分享", "show-legend": "显示图例", + "time-settings": "", "unlink-library-panel": "取消链接库面板", "view": "查看" }, @@ -12235,8 +12254,6 @@ "title": "选择范围" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "未找到与您的查询相关的结果", "recommended": "推荐", @@ -13078,6 +13095,14 @@ "title": "面板" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "最近使用的绝对范围", diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 176b57347ec..6173f865443 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -4137,6 +4137,7 @@ "cancel": "取消", "clear": "清除", "collapse": "收闔", + "disabled": "", "edit": "編輯", "help": "說明", "loading": "正在載入…", @@ -5112,6 +5113,19 @@ "aria-label-remove-override": "移除覆寫", "tooltip-remove-override": "移除覆寫" }, + "panel": { + "time-range-settings": { + "hide-time-info": "", + "hide-time-info-description": "", + "time-from": "", + "time-from-description": "", + "time-shift": "", + "time-shift-description": "", + "time-window-compare": "", + "time-window-compare-description": "", + "title": "" + } + }, "panel-edit": { "alerting-tab": { "dashboard-not-saved": "必須先儲存儀表板,然後才能新增警報。", @@ -5667,6 +5681,11 @@ "open-in-new-tab": "在新的分頁中開啟", "title-error-adding-the-panel": "新增面板時發生錯誤" }, + "add-to-dashboard-form-exposed": { + "title": { + "new-panel": "" + } + }, "annotation-settings-edit": { "back-to-list": "返回清單", "delete": "刪除", @@ -7619,7 +7638,6 @@ } }, "folder-filter": { - "noOptionsMessage-no-folders-found": "未找到資料夾", "select-aria-label": "資料夾篩選條件", "select-placeholder": "按資料夾篩選" }, @@ -10826,6 +10844,7 @@ "replace-library-panel": "取代資料庫面板", "share": "分享", "show-legend": "顯示圖例", + "time-settings": "", "unlink-library-panel": "取消連結資料庫面板", "view": "檢視" }, @@ -12235,8 +12254,6 @@ "title": "選取範圍" }, "tree": { - "collapse": "", - "expand": "", "headline": { "noResults": "未找到您的查詢結果", "recommended": "建議", @@ -13078,6 +13095,14 @@ "title": "面板" } }, + "time-period": { + "1_day": "", + "1_hour": "", + "12_hours": "", + "30_days": "", + "6_hours": "", + "7_days": "" + }, "time-picker": { "absolute": { "recent-title": "最近使用的絕對範圍", From 4c8c32a1d458b8b428ee4114565f9d2e3d27e10f Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 30 Oct 2025 07:25:59 +0300 Subject: [PATCH 30/72] Chore: Update @playwright/test (#113179) --- package.json | 2 +- yarn.lock | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index d74e091448d..373fca80b00 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "@grafana/test-utils": "workspace:*", "@manypkg/get-packages": "^3.0.0", "@npmcli/package-json": "^6.0.0", - "@playwright/test": "1.55.1", + "@playwright/test": "1.56.1", "@pmmmwh/react-refresh-webpack-plugin": "0.6.1", "@react-types/button": "3.13.0", "@react-types/menu": "3.10.3", diff --git a/yarn.lock b/yarn.lock index 7480476abed..792fd086e36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6476,14 +6476,14 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:1.55.1": - version: 1.55.1 - resolution: "@playwright/test@npm:1.55.1" +"@playwright/test@npm:1.56.1": + version: 1.56.1 + resolution: "@playwright/test@npm:1.56.1" dependencies: - playwright: "npm:1.55.1" + playwright: "npm:1.56.1" bin: playwright: cli.js - checksum: 10/c67a46353c58aaeac551bce2654cdef0e9a0ad76b1667514832d34acd4b26ec72f35ea7595cd3fad4c4e1e039d5bb876b8d62c89af4525d455285f6fff9f0642 + checksum: 10/9933fa9f8eb9e775e792421b99c984c310b92092e65de57508ae1951a2589d87bbb5f1c4114bfdf7f69c15c0a3acb3f31259e143fa597aa28e97f4b223e37637 languageName: node linkType: hard @@ -18856,7 +18856,7 @@ __metadata: "@opentelemetry/api": "npm:1.9.0" "@opentelemetry/exporter-collector": "npm:0.25.0" "@opentelemetry/semantic-conventions": "npm:1.37.0" - "@playwright/test": "npm:1.55.1" + "@playwright/test": "npm:1.56.1" "@pmmmwh/react-refresh-webpack-plugin": "npm:0.6.1" "@popperjs/core": "npm:2.11.8" "@react-aria/dialog": "npm:3.5.31" @@ -26334,27 +26334,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.55.1": - version: 1.55.1 - resolution: "playwright-core@npm:1.55.1" +"playwright-core@npm:1.56.1": + version: 1.56.1 + resolution: "playwright-core@npm:1.56.1" bin: playwright-core: cli.js - checksum: 10/953a43039dbcca04513bd3138a9dee249a136d5377da00d49402ffcd24d33ca84dc1dc04636d1b76e9f8c9fd28a302b89cda1ae544d72b5d829c28e623bfcb0b + checksum: 10/df785eb3b3a8392b10dcde5f768e09b7fe459a7b06ed81180da69e048f2154b761f86d79572c2b62037a1f18a44e4ace72f5b6547f4f473b4ab13ab1d94007d2 languageName: node linkType: hard -"playwright@npm:1.55.1": - version: 1.55.1 - resolution: "playwright@npm:1.55.1" +"playwright@npm:1.56.1": + version: 1.56.1 + resolution: "playwright@npm:1.56.1" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.55.1" + playwright-core: "npm:1.56.1" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10/5dcf9ce564cacf6c06ebc864bb2b1f709c641792560d49889ed4c98e230be54a963ec8aaafff11269735d8d22da4900bd2d4ef9f1748d132326ffda8fb1f3f20 + checksum: 10/f1743f93b26f1d497257771428d93f3c9ed2d75b00d935f0cd1556ff2dc61d47f2df8b381d752fbd2c47082b685f0ffe4cc4b7ba440d7b4ba3a08572aec58fba languageName: node linkType: hard From 0b566286222a9c1e167aab0982af007b5925109e Mon Sep 17 00:00:00 2001 From: Anna Urbiztondo Date: Thu, 30 Oct 2025 06:33:26 +0100 Subject: [PATCH 31/72] Docs: Plugins link to catalog (#113103) * Plugins link * Typo * Prettier * Edits * More edits --- .../sources/administration/plugin-management/_index.md | 10 ++++++---- .../sources/breaking-changes/breaking-changes-v11-0.md | 2 +- docs/sources/datasources/_index.md | 3 +-- docs/sources/setup-grafana/configure-grafana/_index.md | 2 +- docs/sources/whatsnew/whats-new-in-v8-0.md | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/sources/administration/plugin-management/_index.md b/docs/sources/administration/plugin-management/_index.md index fce84ec643e..6736e15d176 100644 --- a/docs/sources/administration/plugin-management/_index.md +++ b/docs/sources/administration/plugin-management/_index.md @@ -20,7 +20,7 @@ Plugins enhance your Grafana experience with new ways to connect to and visualiz Read on for an overview on how to get started with plugins: -- Plugins are available in the [plugin catalog](#plugin-catalog). They can be built by Grafana Labs, commercial partners, our community, or you can [build a plugin yourself](/developers/plugin-tools). +- Plugins are available in the [plugin catalog](#access-the-plugin-catalog). They can be built by Grafana Labs, commercial partners, our community, or you can [build a plugin yourself](/developers/plugin-tools). - There are three [types of plugins](#types-of-plugins): panel, data source, and app plugins. - Learn [how to install](#install-a-plugin), [update](#update-a-plugin) and [verify](#verify-your-plugins) your plugins. @@ -40,11 +40,11 @@ Grafana supports three types of plugins: Read more in [Types of plugins](plugin-types). -## Plugin catalog +## Access the Plugin catalog -The Grafana plugin catalog allows you to browse and manage plugins from within Grafana. Only Grafana server administrators and Organization administrators can access and use the plugin catalog. For more information about Grafana roles and permissions, refer to [Roles and permissions](../roles-and-permissions/). +You can install and manage plugins from within Grafana. You need to have a Grafana Server administrator or Organization administrator role to access and use the plugin catalog. For more information about Grafana roles and permissions, refer to [Roles and permissions](../roles-and-permissions/). -The following access rules apply depending on the user role: +For app plugins, the following access rules apply: - If you are an **Org Admin**, you can configure app plugins, but you can't install, uninstall, or update them. - If you are a **Server Admin**, you can't configure app plugins, but you can install, uninstall, or update them. @@ -58,6 +58,8 @@ To browse for available plugins: 1. Use the search box to filter based on name, keywords, organization and other metadata. 1. Click the **Data sources**, **Panels**, or **Applications** buttons to filter by plugin type. +If you're not logged in, you can also access the list of available plugins in the [Plugin catalog](https://grafana.com/grafana/plugins/). + ## Manage your plugins We strongly recommend running the latest plugin version. Use [Grafana Advisor](https://grafana.com/docs/grafana//administration/grafana-advisor) to check the status of your data sources and plugins. diff --git a/docs/sources/breaking-changes/breaking-changes-v11-0.md b/docs/sources/breaking-changes/breaking-changes-v11-0.md index 313015b275d..898673c4be5 100644 --- a/docs/sources/breaking-changes/breaking-changes-v11-0.md +++ b/docs/sources/breaking-changes/breaking-changes-v11-0.md @@ -57,7 +57,7 @@ In Grafana v11, support for the deprecated AngularJS framework is turned off by #### Migration/mitigation -To avoid disruption, ensure all plugins are up to date and migrate from any remaining AngularJS plugins to a React-based alternative. If a plugin relies on AngularJS, a warning icon and message will be displayed in the [plugins catalog](https://grafana.com/docs/grafana//administration/plugin-management/#plugin-catalog) in Grafana and any dashboard panel where it's used. Additionally, a warning banner will appear in any impacted dashboards. A list of all impacted dashboards can also be generated using the [`detect-angular-dashboards`](https://github.com/grafana/detect-angular-dashboards) tool. +To avoid disruption, ensure all plugins are up to date and migrate from any remaining AngularJS plugins to a React-based alternative. If a plugin relies on AngularJS, a warning icon and message will be displayed in the [plugins catalog](https://grafana.com/docs/grafana//administration/plugin-management/#access-the-plugin-catalog) in Grafana and any dashboard panel where it's used. Additionally, a warning banner will appear in any impacted dashboards. A list of all impacted dashboards can also be generated using the [`detect-angular-dashboards`](https://github.com/grafana/detect-angular-dashboards) tool. Our [documentation](https://grafana.com/docs/grafana//developers/angular_deprecation/angular-plugins/) lists all known public plugins and provides migration advice when possible. diff --git a/docs/sources/datasources/_index.md b/docs/sources/datasources/_index.md index 7ba5e1756a5..e7da61ea2f5 100644 --- a/docs/sources/datasources/_index.md +++ b/docs/sources/datasources/_index.md @@ -71,9 +71,8 @@ After you add and configure a data source, you can use it as an input for many o This documentation describes how to manage data sources in general, and how to configure or query the built-in data sources. -For other data sources, refer to the list of [datasource plugins](/grafana/plugins/). -To develop a custom plugin, refer to [Create a data source plugin](#create-a-data-source-plugin). +For other available plugins, refer to the list of [documented plugins](https://grafana.com/docs/plugins/) or browse the [Plugin catalog](/grafana/plugins/). To develop a custom plugin, refer to [Create a data source plugin](#create-a-data-source-plugin). ## Manage data sources diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index f3d3dd89b45..ea83a3bb553 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -2559,7 +2559,7 @@ Available to Grafana administrators only, enables installing, uninstalling, and Set to `true` by default. Setting it to `false` hides the controls. -For more information, refer to [Plugin catalog](../../administration/plugin-management/#plugin-catalog). +For more information, refer to [Plugin catalog](../../administration/plugin-management/#access-the-plugin-catalog). #### `plugin_admin_external_manage_enabled` diff --git a/docs/sources/whatsnew/whats-new-in-v8-0.md b/docs/sources/whatsnew/whats-new-in-v8-0.md index cf3792d8a48..7559f646678 100644 --- a/docs/sources/whatsnew/whats-new-in-v8-0.md +++ b/docs/sources/whatsnew/whats-new-in-v8-0.md @@ -160,7 +160,7 @@ Log navigation in Explore has been significantly improved. We added pagination t You can now use the Plugin catalog app to easily manage your plugins from within Grafana. Install, update, and uninstall plugins without requiring a server restart. -[Plugin catalog](../../administration/plugin-management/#plugin-catalog) was added as a result of this feature. +[Plugin catalog](../../administration/plugin-management/#access-the-plugin-catalog) was added as a result of this feature. ### Performance improvements From 84edc45dee106efc33198b352ecaee03bdaf4238 Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Thu, 30 Oct 2025 01:49:15 -0400 Subject: [PATCH 32/72] PreviewBannerViewPR: Display branch info in preview banner (#113195) --- .../Dashboards/DashboardPreviewBanner.tsx | 22 ++++++++++---- .../components/Shared/PreviewBannerViewPR.tsx | 29 ++++++++++++++++--- .../provisioning/components/utils/url.ts | 17 +++++++++++ public/locales/en-US/grafana.json | 1 + 4 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 public/app/features/provisioning/components/utils/url.ts diff --git a/public/app/features/provisioning/components/Dashboards/DashboardPreviewBanner.tsx b/public/app/features/provisioning/components/Dashboards/DashboardPreviewBanner.tsx index 77df93f0f8b..2f39623f29a 100644 --- a/public/app/features/provisioning/components/Dashboards/DashboardPreviewBanner.tsx +++ b/public/app/features/provisioning/components/Dashboards/DashboardPreviewBanner.tsx @@ -6,7 +6,8 @@ import { DashboardPageRouteSearchParams } from 'app/features/dashboard/container import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequestParam'; import { DashboardRoutes } from 'app/types/dashboard'; -import { PreviewBannerViewPR } from '../Shared/PreviewBannerViewPR'; +import { useGetResourceRepositoryView } from '../../hooks/useGetResourceRepositoryView'; +import { PreviewBranchInfo, PreviewBannerViewPR } from '../Shared/PreviewBannerViewPR'; export interface CommonBannerProps { queryParams: DashboardPageRouteSearchParams; @@ -28,6 +29,15 @@ export const commonAlertProps = { function DashboardPreviewBannerContent({ queryParams, slug, path }: DashboardPreviewBannerContentProps) { const { prURL } = usePullRequestParam(); const file = useGetRepositoryFilesWithPathQuery({ name: slug, path, ref: queryParams.ref }); + const { repository } = useGetResourceRepositoryView({ name: slug }); + const targetRef = file.data?.ref; + const repoBaseUrl = file.data?.urls?.repositoryURL; + + const branchInfo: PreviewBranchInfo = { + targetBranch: targetRef, + configuredBranch: repository?.branch, + repoBaseUrl, + }; if (file.data?.errors) { return ( @@ -45,13 +55,13 @@ function DashboardPreviewBannerContent({ queryParams, slug, path }: DashboardPre // This page was loaded with a `pull_request_url` in the URL if (prURL?.length) { - return ; + return ; } - // Check if this is a repo link - const repoUrl = file.data?.urls?.newPullRequestURL ?? file.data?.urls?.compareURL; - if (repoUrl) { - return ; + // Check if pull request URLs are available from the repository file data + const prOrCompareUrl = file.data?.urls?.newPullRequestURL ?? file.data?.urls?.compareURL; + if (prOrCompareUrl) { + return ; } return ( diff --git a/public/app/features/provisioning/components/Shared/PreviewBannerViewPR.tsx b/public/app/features/provisioning/components/Shared/PreviewBannerViewPR.tsx index e9cddc16231..8a796ee049c 100644 --- a/public/app/features/provisioning/components/Shared/PreviewBannerViewPR.tsx +++ b/public/app/features/provisioning/components/Shared/PreviewBannerViewPR.tsx @@ -1,25 +1,32 @@ import { textUtil } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { Alert, Icon, Stack } from '@grafana/ui'; +import { Alert, Box, Icon, Stack, TextLink } from '@grafana/ui'; import { RepoTypeDisplay, RepoType } from 'app/features/provisioning/Wizard/types'; import { usePullRequestParam } from 'app/features/provisioning/hooks/usePullRequestParam'; import { commonAlertProps } from '../Dashboards/DashboardPreviewBanner'; - -// TODO: We have this https://github.com/grafana/git-ui-sync-project/issues/166 to add more details about the PR. +import { getBranchUrl } from '../utils/url'; interface Props { prParam?: string; isNewPr?: boolean; behindBranch?: boolean; repoUrl?: string; + branchInfo?: PreviewBranchInfo; } +export type PreviewBranchInfo = { + targetBranch?: string; + configuredBranch?: string; + repoBaseUrl?: string; +}; + /** * @description This component is used to display a banner when a provisioned dashboard/folder is created or loaded from a new branch in repo. */ -export function PreviewBannerViewPR({ prParam, isNewPr, behindBranch, repoUrl }: Props) { +export function PreviewBannerViewPR({ prParam, isNewPr, behindBranch, repoUrl, branchInfo }: Props) { const { repoType } = usePullRequestParam(); + const { targetBranch, configuredBranch, repoBaseUrl } = branchInfo || {}; const capitalizedRepoType = isValidRepoType(repoType) ? RepoTypeDisplay[repoType] : 'repository'; @@ -96,6 +103,15 @@ export function PreviewBannerViewPR({ prParam, isNewPr, behindBranch, repoUrl }: The rest of Grafana users in your organization will still see the current version saved to configured default branch until this branch is merged + + {/* when repo type is not local, we show branch information */} + {showBranchInfo(repoType, branchInfo) && ( + + branch: + {targetBranch} {'\u2192'}{' '} + {configuredBranch} + + )} ); } @@ -106,3 +122,8 @@ export function isValidRepoType(repoType: string | undefined): repoType is RepoT } return repoType in RepoTypeDisplay; } + +function showBranchInfo(repoType: string | undefined, branchInfo?: PreviewBranchInfo): boolean { + const { targetBranch, configuredBranch, repoBaseUrl } = branchInfo || {}; + return repoType !== 'local' && !!targetBranch && !!configuredBranch && !!repoBaseUrl; +} diff --git a/public/app/features/provisioning/components/utils/url.ts b/public/app/features/provisioning/components/utils/url.ts new file mode 100644 index 00000000000..286235db636 --- /dev/null +++ b/public/app/features/provisioning/components/utils/url.ts @@ -0,0 +1,17 @@ +// repoType = string because this repoType is coming from URL param +export const getBranchUrl = (baseUrl: string, branch: string, repoType?: string): string => { + if (repoType === 'local') { + return ''; + } + + switch (repoType) { + case 'github': + return `${baseUrl}/tree/${branch}`; + case 'gitlab': + return `${baseUrl}/-/tree/${branch}`; + case 'bitbucket': + return `${baseUrl}/src/${branch}`; + default: + return ''; + } +}; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 3c0c17c7af2..4e435b87ed2 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11370,6 +11370,7 @@ "provisioned-resource-preview-banner": { "preview-banner": { "behind-branch-text": "This resource is behind the branch in {{repoType}}.", + "branch-text": "branch:", "not-saved": "The rest of Grafana users in your organization will still see the current version saved to configured default branch until this branch is merged", "open-in-repo-button": "Open in {{repoType}}", "open-pull-request-in-repo": "Open pull request in {{repoType}}", From 209aa13ff75535dd548d62cdf3febfda26f955eb Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Thu, 30 Oct 2025 07:25:55 +0100 Subject: [PATCH 33/72] Dashboard performance analytics system with Scenes integration (#112137) * Dashboard performance profiling architecture improvements - Create shared performanceUtils.ts with type-safe performance.memory access - Add standardized grouped logging utilities for structured console output - Convert observer methods to arrow functions eliminating constructor bindings - Implement DashboardAnalyticsAggregator for comprehensive panel metrics - Add ScenePerformanceLogger for performance marks and measurements - Create DashboardAnalyticsInitializerBehavior for automatic profiling setup - Update dashboard scene integration to use improved profiling system - Add numeric duration logging for better programmatic analysis - Fix localStorage usage to use @grafana/data store for consistency - Consolidate performance tracking logic into shared utilities * canary scenes * tests/lint * docs * performanceUtils namespace * Review and sync scenes * Only enable dashboard profiling when needed * docs update * update scenes --------- Co-authored-by: Victor Marin --- package.json | 4 +- .../DashboardAnalyticsInitializerBehavior.ts | 37 + .../pages/DashboardScenePageStateManager.ts | 31 + .../dashboard-scene/scene/DashboardScene.tsx | 4 + .../transformSaveModelSchemaV2ToScene.ts | 25 +- .../transformSaveModelToScene.ts | 31 +- .../PublicDashboardPageProxy.test.tsx | 3 + .../services/DashboardAnalyticsAggregator.ts | 373 ++++++++++ .../dashboard/services/DashboardProfiler.ts | 68 +- .../services/ScenePerformanceLogger.ts | 220 ++++++ .../dashboard-render-performance-profiling.md | 657 +++++++++++++----- .../services/performanceConstants.ts | 82 +++ .../dashboard/services/performanceUtils.ts | 139 ++++ .../scopes/selector/ScopesSelectorService.ts | 5 +- .../scopes/tests/dashboardReload.test.ts | 1 + yarn.lock | 80 +-- 16 files changed, 1483 insertions(+), 277 deletions(-) create mode 100644 public/app/features/dashboard-scene/behaviors/DashboardAnalyticsInitializerBehavior.ts create mode 100644 public/app/features/dashboard/services/DashboardAnalyticsAggregator.ts create mode 100644 public/app/features/dashboard/services/ScenePerformanceLogger.ts create mode 100644 public/app/features/dashboard/services/performanceConstants.ts create mode 100644 public/app/features/dashboard/services/performanceUtils.ts diff --git a/package.json b/package.json index 373fca80b00..a68dd4130c0 100644 --- a/package.json +++ b/package.json @@ -296,8 +296,8 @@ "@grafana/plugin-ui": "^0.10.10", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^6.40.1", - "@grafana/scenes-react": "^6.40.1", + "@grafana/scenes": "^6.41.0", + "@grafana/scenes-react": "^6.41.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/features/dashboard-scene/behaviors/DashboardAnalyticsInitializerBehavior.ts b/public/app/features/dashboard-scene/behaviors/DashboardAnalyticsInitializerBehavior.ts new file mode 100644 index 00000000000..2cc1e1138e0 --- /dev/null +++ b/public/app/features/dashboard-scene/behaviors/DashboardAnalyticsInitializerBehavior.ts @@ -0,0 +1,37 @@ +import { writePerformanceLog } from '@grafana/scenes'; + +import { getDashboardAnalyticsAggregator } from '../../dashboard/services/DashboardAnalyticsAggregator'; +import { DashboardScene } from '../scene/DashboardScene'; + +/** + * Scene behavior function that manages the dashboard-specific initialization + * of the global analytics aggregator for each dashboard session. + * + * Note: Both ScenePerformanceLogger and DashboardAnalyticsAggregator are now + * initialized globally to avoid timing issues. This behavior only sets + * dashboard-specific context. + */ +export function dashboardAnalyticsInitializer(dashboard: DashboardScene) { + const { uid, title } = dashboard.state; + + if (!uid) { + console.warn('dashboardAnalyticsInitializer: Dashboard UID is missing'); + return; + } + + writePerformanceLog('DAI', 'Setting dashboard context for analytics aggregator'); + + // Set dashboard context on the global aggregator (observer already registered) + const aggregator = getDashboardAnalyticsAggregator(); + aggregator.initialize(uid, title || 'Untitled Dashboard'); + + writePerformanceLog('DAI', 'Dashboard analytics aggregator context set:', { uid, title }); + + // Return cleanup function + return () => { + // Only clear dashboard state, keep observer registered for next dashboard + aggregator.destroy(); + + writePerformanceLog('DAI', 'Dashboard analytics aggregator context cleared'); + }; +} diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 54dd386c619..e17f7e36cff 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -17,8 +17,11 @@ import { import { ensureV2Response, transformDashboardV2SpecToV1 } from 'app/features/dashboard/api/ResponseTransformers'; import { DashboardVersionError, DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; import { isDashboardV2Resource, isDashboardV2Spec, isV2StoredVersion } from 'app/features/dashboard/api/utils'; +import { initializeDashboardAnalyticsAggregator } from 'app/features/dashboard/services/DashboardAnalyticsAggregator'; import { dashboardLoaderSrv, DashboardLoaderSrvV2 } from 'app/features/dashboard/services/DashboardLoaderSrv'; +import { getDashboardSceneProfiler } from 'app/features/dashboard/services/DashboardProfiler'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { initializeScenePerformanceLogger } from 'app/features/dashboard/services/ScenePerformanceLogger'; import { emitDashboardViewEvent } from 'app/features/dashboard/state/analyticsProcessor'; import { trackDashboardSceneLoaded } from 'app/features/dashboard-scene/utils/tracking'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; @@ -41,6 +44,14 @@ import { restoreDashboardStateFromLocalStorage } from '../utils/dashboardSession import { processQueryParamsForDashboardLoad, updateNavModel } from './utils'; +/** + * Initialize both performance services to ensure they're ready before profiling starts + */ +function initializeDashboardPerformanceServices(): void { + initializeScenePerformanceLogger(); + initializeDashboardAnalyticsAggregator(); +} + export interface LoadError { status?: number; messageId?: string; @@ -296,6 +307,16 @@ abstract class DashboardScenePageStateManagerBase const queryController = sceneGraph.getQueryController(dashboard); trackDashboardSceneLoaded(dashboard, measure?.duration); + + const enableProfiling = + config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === options.uid) !== -1; + + if (enableProfiling) { + // Initialize both performance services before starting profiling to ensure observers are registered + initializeDashboardPerformanceServices(); + } + + // Start dashboard_view profiling (both services are now guaranteed to be listening) queryController?.startProfile('dashboard_view'); if (options.route !== DashboardRoutes.New) { @@ -409,6 +430,11 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag fromCache.state.version === rsp?.dashboard.version && fromCache.state.meta.created === rsp?.meta.created ) { + const profiler = getDashboardSceneProfiler(); + profiler.setMetadata({ + dashboardUID: fromCache.state.uid, + dashboardTitle: fromCache.state.title, + }); return fromCache; } @@ -696,6 +722,11 @@ export class DashboardScenePageStateManagerV2 extends DashboardScenePageStateMan const fromCache = this.getSceneFromCache(options.uid); if (fromCache && fromCache.state.version === rsp?.metadata.generation) { + const profiler = getDashboardSceneProfiler(); + profiler.setMetadata({ + dashboardUID: fromCache.state.uid, + dashboardTitle: fromCache.state.title, + }); return fromCache; } diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 748d3e7986e..087cd505fcd 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -25,6 +25,7 @@ import store from 'app/core/store'; import { sortedDeepCloneWithoutNulls } from 'app/core/utils/object'; import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; import { SaveDashboardAsOptions } from 'app/features/dashboard/components/SaveDashboard/types'; +import { getDashboardSceneProfiler } from 'app/features/dashboard/services/DashboardProfiler'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { DashboardModel, ScopeMeta } from 'app/features/dashboard/state/DashboardModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; @@ -625,7 +626,10 @@ export class DashboardScene extends SceneObjectBase impleme } public onCreateNewPanel(): VizPanel { + const profiler = getDashboardSceneProfiler(); const vizPanel = getDefaultVizPanel(); + profiler.attachProfilerToPanel(vizPanel); + this.addPanel(vizPanel); return vizPanel; } diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index ad94f404e68..7d57a2b087f 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -56,13 +56,14 @@ import { } from 'app/features/apiserver/types'; import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; import { + getDashboardSceneProfilerWithMetadata, + enablePanelProfilingForDashboard, getDashboardComponentInteractionCallback, - getDashboardInteractionCallback, - getDashboardSceneProfiler, } from 'app/features/dashboard/services/DashboardProfiler'; import { DashboardMeta } from 'app/types/dashboard'; import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior'; +import { dashboardAnalyticsInitializer } from '../behaviors/DashboardAnalyticsInitializerBehavior'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; @@ -168,22 +169,24 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo uid === '*' || uid === metadata.name) !== -1; const queryController = new behaviors.SceneQueryController( { - enableProfiling: - config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === metadata.name) !== -1, - onProfileComplete: getDashboardInteractionCallback(metadata.name, dashboard.title), + enableProfiling, }, - getDashboardSceneProfiler() + dashboardProfiler ); const interactionTracker = new behaviors.SceneInteractionTracker( { - enableInteractionTracking: - config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === metadata.name) !== -1, + enableInteractionTracking: enableProfiling, onInteractionComplete: getDashboardComponentInteractionCallback(metadata.name, dashboard.title), }, - getDashboardSceneProfiler() + dashboardProfiler ); const dashboardScene = new DashboardScene( @@ -223,6 +226,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo uid === '*' || uid === oldModel.uid) !== -1; const queryController = new behaviors.SceneQueryController( { - enableProfiling: - config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === oldModel.uid) !== -1, - onProfileComplete: getDashboardInteractionCallback(oldModel.uid, oldModel.title), + enableProfiling, }, - getDashboardSceneProfiler() + dashboardProfiler ); const interactionTracker = new behaviors.SceneInteractionTracker( { - enableInteractionTracking: - config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === oldModel.uid) !== -1, + enableInteractionTracking: enableProfiling, onInteractionComplete: getDashboardComponentInteractionCallback(oldModel.uid, oldModel.title), }, - getDashboardSceneProfiler() + dashboardProfiler ); const behaviorList: SceneObjectState['$behaviors'] = [ @@ -330,6 +334,12 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, }), ]; + if (enableProfiling) { + // Analytics aggregator lifecycle management (initialization, observer registration, cleanup) + behaviorList.push(dashboardAnalyticsInitializer); + } + // Will be enabled in the dashboard creation below + let body: DashboardLayoutManager; if (config.featureToggles.dashboardNewLayouts && oldModel.panels.some((p) => p.type === 'row')) { @@ -385,6 +395,9 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, serializerVersion ); + // Enable panel profiling for this dashboard using the composed SceneRenderProfiler + enablePanelProfilingForDashboard(dashboardScene, uid); + return dashboardScene; } diff --git a/public/app/features/dashboard/containers/PublicDashboardPageProxy.test.tsx b/public/app/features/dashboard/containers/PublicDashboardPageProxy.test.tsx index b8ca09b1b4e..0bb44738440 100644 --- a/public/app/features/dashboard/containers/PublicDashboardPageProxy.test.tsx +++ b/public/app/features/dashboard/containers/PublicDashboardPageProxy.test.tsx @@ -51,6 +51,9 @@ describe('PublicDashboardPageProxy', () => { beforeEach(() => { config.featureToggles.publicDashboardsScene = false; + // Mock console methods to avoid jest-fail-on-console issues + jest.spyOn(console, 'warn').mockImplementation(); + // Mock the dashboard UID response so we don't get any refused connection errors // from this test (as the fetch polyfill means this logic would actually try and call the API) // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/public/app/features/dashboard/services/DashboardAnalyticsAggregator.ts b/public/app/features/dashboard/services/DashboardAnalyticsAggregator.ts new file mode 100644 index 00000000000..8671c2b13e5 --- /dev/null +++ b/public/app/features/dashboard/services/DashboardAnalyticsAggregator.ts @@ -0,0 +1,373 @@ +import { logMeasurement, reportInteraction } from '@grafana/runtime'; +import { performanceUtils } from '@grafana/scenes'; + +import { SLOW_OPERATION_THRESHOLD_MS } from './performanceConstants'; +import { + registerPerformanceObserver, + getPerformanceMemory, + writePerformanceGroupStart, + writePerformanceGroupLog, + writePerformanceGroupEnd, +} from './performanceUtils'; + +/** + * Panel metrics structure for analytics + */ +interface PanelAnalyticsMetrics { + panelId: string; + panelKey: string; + pluginId: string; + pluginVersion?: string; + totalQueryTime: number; + totalFieldConfigTime: number; + totalTransformationTime: number; + totalRenderTime: number; + pluginLoadTime: number; + queryOperations: Array<{ + duration: number; + timestamp: number; + queryType?: string; + seriesCount?: number; + dataPointsCount?: number; + }>; + fieldConfigOperations: Array<{ + duration: number; + timestamp: number; + }>; + transformationOperations: Array<{ + duration: number; + timestamp: number; + transformationId?: string; + success?: boolean; + outputSeriesCount?: number; + }>; + renderOperations: Array<{ + duration: number; + timestamp: number; + }>; +} + +/** + * Aggregates Scene performance events into analytics-ready panel metrics + */ +export class DashboardAnalyticsAggregator implements performanceUtils.ScenePerformanceObserver { + private panelMetrics = new Map(); + private dashboardUID = ''; + private dashboardTitle = ''; + + public initialize(uid: string, title: string) { + // Clear previous dashboard data and set new context + this.panelMetrics.clear(); + this.dashboardUID = uid; + this.dashboardTitle = title; + } + + public destroy() { + // Clear dashboard context + this.panelMetrics.clear(); + this.dashboardUID = ''; + this.dashboardTitle = ''; + } + + /** + * Clear all collected metrics (called on dashboard interaction start) + */ + public clearMetrics() { + this.panelMetrics.clear(); + } + + /** + * Get aggregated panel metrics for analytics + */ + public getPanelMetrics(): PanelAnalyticsMetrics[] { + return Array.from(this.panelMetrics.values()); + } + + // Dashboard-level events (we don't need to track these for panel analytics) + onDashboardInteractionStart = (data: performanceUtils.DashboardInteractionStartData): void => { + // Clear metrics when new dashboard interaction starts + this.clearMetrics(); + }; + + onDashboardInteractionMilestone = (_data: performanceUtils.DashboardInteractionMilestoneData): void => { + // No action needed for milestones in analytics + }; + + onDashboardInteractionComplete = (data: performanceUtils.DashboardInteractionCompleteData): void => { + // Send analytics report for dashboard interaction completion + this.sendAnalyticsReport(data); + }; + + // Panel-level events + onPanelOperationStart = (data: performanceUtils.PanelPerformanceData): void => { + // Start events don't need aggregation, just ensure panel exists + this.ensurePanelExists(data.panelKey, data.panelId, data.pluginId, data.pluginVersion); + }; + + onPanelOperationComplete = (data: performanceUtils.PanelPerformanceData): void => { + // Aggregate panel metrics without verbose logging (handled by ScenePerformanceLogger) + const panel = this.panelMetrics.get(data.panelKey); + if (!panel) { + console.warn('Panel not found for operation completion:', data.panelKey); + return; + } + + const duration = data.duration || 0; + + switch (data.operation) { + case 'fieldConfig': + panel.totalFieldConfigTime += duration; + panel.fieldConfigOperations.push({ + duration, + timestamp: data.timestamp, + }); + break; + + case 'transform': + panel.totalTransformationTime += duration; + panel.transformationOperations.push({ + duration, + timestamp: data.timestamp, + transformationId: data.metadata.transformationId, + success: data.metadata.success, + }); + break; + + case 'query': + panel.totalQueryTime += duration; + panel.queryOperations.push({ + duration, + timestamp: data.timestamp, + queryType: data.metadata.queryType, + }); + break; + + case 'render': + panel.totalRenderTime += duration; + panel.renderOperations.push({ + duration, + timestamp: data.timestamp, + }); + break; + + case 'plugin-load': + panel.pluginLoadTime += duration; + break; + } + }; + + // Query-level events + onQueryStart = (_data: performanceUtils.QueryPerformanceData): void => { + // no-op + }; + + onQueryComplete = (_data: performanceUtils.QueryPerformanceData): void => { + // no-op + }; + + /** + * Ensure a panel exists in our tracking map + */ + private ensurePanelExists( + panelKey: string, + panelId: string, + pluginId: string, + pluginVersion?: string + ): PanelAnalyticsMetrics { + let panel = this.panelMetrics.get(panelKey); + if (!panel) { + panel = { + panelId, + panelKey, + pluginId, + pluginVersion, + totalQueryTime: 0, + totalFieldConfigTime: 0, + totalTransformationTime: 0, + totalRenderTime: 0, + pluginLoadTime: 0, + queryOperations: [], + fieldConfigOperations: [], + transformationOperations: [], + renderOperations: [], + }; + this.panelMetrics.set(panelKey, panel); + } + return panel; + } + + /** + * Send analytics report for dashboard interactions + */ + private sendAnalyticsReport(data: performanceUtils.DashboardInteractionCompleteData): void { + const payload = { + duration: data.duration || 0, + networkDuration: data.networkDuration || 0, + startTs: data.timestamp, + endTs: data.timestamp + (data.duration || 0), + timeSinceBoot: performance.measure('time_since_boot', 'frontend_boot_js_done_time_seconds').duration, + longFramesCount: data.longFramesCount, + longFramesTotalTime: data.longFramesTotalTime, + ...getPerformanceMemory(), + }; + + const panelMetrics = this.getPanelMetrics(); + + this.logDashboardAnalyticsEvent(data, payload, panelMetrics); + + reportInteraction('dashboard_render', { + interactionType: data.interactionType, + uid: this.dashboardUID, + ...payload, + }); + + logMeasurement('dashboard_render', payload, { + interactionType: data.interactionType, + dashboard: this.dashboardUID, + title: this.dashboardTitle, + }); + } + + /** + * Log dashboard analytics event with panel metrics and performance insights + */ + private logDashboardAnalyticsEvent( + data: performanceUtils.DashboardInteractionCompleteData, + payload: Record, + panelMetrics: PanelAnalyticsMetrics[] | null + ): void { + const panelCount = panelMetrics?.length || 0; + const panelSummary = panelCount ? `${panelCount} panels analyzed` : 'No panel metrics'; + + // Main analytics summary + const slowPanelCount = + panelMetrics?.filter( + (p) => + p.totalQueryTime + p.totalTransformationTime + p.totalRenderTime + p.totalFieldConfigTime + p.pluginLoadTime > + SLOW_OPERATION_THRESHOLD_MS + ).length || 0; + + writePerformanceGroupStart( + 'DAA', + `[ANALYTICS] ${data.interactionType} | ${panelSummary}${slowPanelCount > 0 ? ` | ${slowPanelCount} slow panels ⚠️` : ''}` + ); + + // Dashboard overview + writePerformanceGroupLog('DAA', '📊 Dashboard (ms):', { + duration: Math.round((data.duration || 0) * 10) / 10, + network: Math.round((data.networkDuration || 0) * 10) / 10, + interactionType: data.interactionType, + slowPanels: slowPanelCount, + }); + + // Analytics payload + writePerformanceGroupLog('DAA', '📈 Analytics payload:', payload); + + // Individual collapsible panel logs with detailed breakdown + if (panelMetrics && panelMetrics.length > 0) { + panelMetrics.forEach((panel) => { + const totalPanelTime = + panel.totalQueryTime + + panel.totalTransformationTime + + panel.totalRenderTime + + panel.totalFieldConfigTime + + panel.pluginLoadTime; + + const isSlowPanel = totalPanelTime > SLOW_OPERATION_THRESHOLD_MS; + const slowWarning = isSlowPanel ? ' ⚠️ SLOW' : ''; + + writePerformanceGroupStart( + 'DAA', + `🎨 Panel ${panel.pluginId}-${panel.panelId}: ${totalPanelTime.toFixed(1)}ms total${slowWarning}` + ); + + writePerformanceGroupLog('DAA', '🔧 Plugin:', { + id: panel.pluginId, + version: panel.pluginVersion || 'unknown', + panelId: panel.panelId, + panelKey: panel.panelKey, + }); + + writePerformanceGroupLog('DAA', '⚡ Performance (ms):', { + totalTime: Math.round(totalPanelTime * 10) / 10, // Round to 1 decimal + isSlowPanel: isSlowPanel, + breakdown: { + query: Math.round(panel.totalQueryTime * 10) / 10, + transform: Math.round(panel.totalTransformationTime * 10) / 10, + render: Math.round(panel.totalRenderTime * 10) / 10, + fieldConfig: Math.round(panel.totalFieldConfigTime * 10) / 10, + pluginLoad: Math.round(panel.pluginLoadTime * 10) / 10, + }, + }); + + if (panel.queryOperations.length > 0) { + writePerformanceGroupLog('DAA', '📊 Queries:', { + count: panel.queryOperations.length, + details: panel.queryOperations.map((op, index) => ({ + operation: index + 1, + duration: Math.round(op.duration * 10) / 10, + timestamp: op.timestamp, + queryType: op.queryType || 'unknown', + })), + }); + } + + if (panel.transformationOperations.length > 0) { + writePerformanceGroupLog('DAA', '🔄 Transformations:', { + count: panel.transformationOperations.length, + details: panel.transformationOperations.map((op, index) => ({ + operation: index + 1, + duration: Math.round(op.duration * 10) / 10, + timestamp: op.timestamp, + transformationId: op.transformationId || 'unknown', + success: op.success !== false, + })), + }); + } + + if (panel.renderOperations.length > 0) { + writePerformanceGroupLog('DAA', '🎨 Renders:', { + count: panel.renderOperations.length, + details: panel.renderOperations.map((op, index) => ({ + operation: index + 1, + duration: Math.round(op.duration * 10) / 10, + timestamp: op.timestamp, + })), + }); + } + + if (panel.fieldConfigOperations.length > 0) { + writePerformanceGroupLog('DAA', '⚙️ FieldConfigs:', { + count: panel.fieldConfigOperations.length, + details: panel.fieldConfigOperations.map((op, index) => ({ + operation: index + 1, + duration: Math.round(op.duration * 10) / 10, + timestamp: op.timestamp, + })), + }); + } + + writePerformanceGroupEnd(); + }); + } + + writePerformanceGroupEnd(); + } +} + +// Global singleton instance with lazy initialization +let dashboardAnalyticsAggregator: DashboardAnalyticsAggregator | null = null; + +export function initializeDashboardAnalyticsAggregator(): DashboardAnalyticsAggregator { + if (!dashboardAnalyticsAggregator) { + dashboardAnalyticsAggregator = new DashboardAnalyticsAggregator(); + + // Register as global performance observer + registerPerformanceObserver(dashboardAnalyticsAggregator, 'DAA'); + } + return dashboardAnalyticsAggregator; +} + +export function getDashboardAnalyticsAggregator(): DashboardAnalyticsAggregator { + return initializeDashboardAnalyticsAggregator(); +} diff --git a/public/app/features/dashboard/services/DashboardProfiler.ts b/public/app/features/dashboard/services/DashboardProfiler.ts index 1be6cfd1f1c..01fe67eb451 100644 --- a/public/app/features/dashboard/services/DashboardProfiler.ts +++ b/public/app/features/dashboard/services/DashboardProfiler.ts @@ -1,11 +1,24 @@ -import { logMeasurement, reportInteraction } from '@grafana/runtime'; -import { SceneInteractionProfileEvent, SceneRenderProfiler } from '@grafana/scenes'; +import { logMeasurement, reportInteraction, config } from '@grafana/runtime'; +import { performanceUtils, type SceneObject } from '@grafana/scenes'; -let dashboardSceneProfiler: SceneRenderProfiler | undefined; +interface SceneInteractionProfileEvent { + origin: string; + duration: number; + networkDuration: number; + startTs: number; + endTs: number; +} + +let dashboardSceneProfiler: performanceUtils.SceneRenderProfiler | undefined; export function getDashboardSceneProfiler() { if (!dashboardSceneProfiler) { - dashboardSceneProfiler = new SceneRenderProfiler(); + // Create panel profiling configuration + const panelProfilingConfig = { + watchStateKey: 'body', // Watch dashboard body changes for panel structure changes + }; + + dashboardSceneProfiler = new performanceUtils.SceneRenderProfiler(panelProfilingConfig); } return dashboardSceneProfiler; } @@ -30,28 +43,31 @@ export function getDashboardComponentInteractionCallback(uid: string, title: str }; } -export function getDashboardInteractionCallback(uid: string, title: string) { - return (e: SceneInteractionProfileEvent) => { - const payload = { - duration: e.duration, - networkDuration: e.networkDuration, - processingTime: e.duration - e.networkDuration, - startTs: e.startTs, - endTs: e.endTs, - totalJSHeapSize: e.totalJSHeapSize, - usedJSHeapSize: e.usedJSHeapSize, - jsHeapSizeLimit: e.jsHeapSizeLimit, - longFramesCount: e.longFramesCount, - longFramesTotalTime: e.longFramesTotalTime, - timeSinceBoot: performance.measure('time_since_boot', 'frontend_boot_js_done_time_seconds').duration, - }; +// Enhanced function to create profiler with dashboard metadata +export function getDashboardSceneProfilerWithMetadata(uid: string, title: string) { + const profiler = getDashboardSceneProfiler(); - reportInteraction('dashboard_render', { - interactionType: e.origin, - uid, - ...payload, - }); + // Set metadata for observer notifications + profiler.setMetadata({ + dashboardUID: uid, + dashboardTitle: title, + }); - logMeasurement(`dashboard_render`, payload, { interactionType: e.origin, dashboard: uid, title: title }); - }; + // Note: Analytics aggregator initialization and observer registration + // is now handled by DashboardAnalyticsInitializerBehavior + + return profiler; +} + +// Function to enable panel profiling for a specific dashboard +export function enablePanelProfilingForDashboard(dashboard: SceneObject, uid: string) { + // Check if panel profiling should be enabled for this dashboard + const shouldEnablePanelProfiling = + config.dashboardPerformanceMetrics.findIndex((configUid) => configUid === '*' || configUid === uid) !== -1; + + if (shouldEnablePanelProfiling) { + const profiler = getDashboardSceneProfiler(); + // Attach panel profiling to this dashboard + profiler.attachPanelProfiling(dashboard); + } } diff --git a/public/app/features/dashboard/services/ScenePerformanceLogger.ts b/public/app/features/dashboard/services/ScenePerformanceLogger.ts new file mode 100644 index 00000000000..79b0bceb402 --- /dev/null +++ b/public/app/features/dashboard/services/ScenePerformanceLogger.ts @@ -0,0 +1,220 @@ +import { performanceUtils, writePerformanceLog } from '@grafana/scenes'; + +import { PERFORMANCE_MARKS, PERFORMANCE_MEASURES, SLOW_OPERATION_THRESHOLD_MS } from './performanceConstants'; +import { registerPerformanceObserver, createPerformanceMark, createPerformanceMeasure } from './performanceUtils'; + +/** + * Grafana logger that subscribes to Scene performance events + * and logs them to console with Chrome DevTools performance marks and measurements for debugging. + */ +export class ScenePerformanceLogger implements performanceUtils.ScenePerformanceObserver { + private panelGroupsOpen = new Set(); // Track which panels we've seen + + public initialize() { + writePerformanceLog('SPL', 'Performance logger ready'); + } + + public destroy() { + this.panelGroupsOpen.clear(); + writePerformanceLog('SPL', 'Performance logger state cleared'); + } + + // Dashboard-level events + onDashboardInteractionStart = (data: performanceUtils.DashboardInteractionStartData): void => { + const dashboardStartMark = PERFORMANCE_MARKS.DASHBOARD_INTERACTION_START(data.operationId); + createPerformanceMark(dashboardStartMark, data.timestamp); + + const title = data.metadata?.dashboardTitle || 'Unknown Dashboard'; + + writePerformanceLog('SPL', `[DASHBOARD] ${data.interactionType} started: ${title}`); + }; + + onDashboardInteractionMilestone = (data: performanceUtils.DashboardInteractionMilestoneData): void => { + const milestone = data.milestone || 'unknown'; + const dashboardMilestoneMark = PERFORMANCE_MARKS.DASHBOARD_MILESTONE(data.operationId, milestone); + createPerformanceMark(dashboardMilestoneMark, data.timestamp); + }; + + onDashboardInteractionComplete = (data: performanceUtils.DashboardInteractionCompleteData): void => { + const dashboardEndMark = PERFORMANCE_MARKS.DASHBOARD_INTERACTION_END(data.operationId); + const dashboardStartMark = PERFORMANCE_MARKS.DASHBOARD_INTERACTION_START(data.operationId); + const dashboardMeasureName = PERFORMANCE_MEASURES.DASHBOARD_INTERACTION(data.operationId); + + createPerformanceMark(dashboardEndMark, data.timestamp); + createPerformanceMeasure(dashboardMeasureName, dashboardStartMark, dashboardEndMark); + + this.panelGroupsOpen.clear(); + }; + + onPanelOperationStart = (data: performanceUtils.PanelPerformanceData): void => { + this.createStandardizedPanelMark(data, 'start'); + + // Track panel for summary logging later + this.panelGroupsOpen.add(data.panelKey); + }; + + onPanelOperationComplete = (data: performanceUtils.PanelPerformanceData): void => { + this.createStandardizedPanelMark(data, 'end'); + this.createStandardizedPanelMeasure(data); + + const duration = (data.duration || 0).toFixed(1); + const slowWarning = (data.duration || 0) > SLOW_OPERATION_THRESHOLD_MS ? ' ⚠️ SLOW' : ''; + + // For query operations, include the queryId for correlation + let operationDisplay: string = data.operation; + if (data.operation === 'query') { + operationDisplay = `${data.operation} [${data.metadata.queryId}]`; + } + + writePerformanceLog( + 'SPL', + `[PANEL] ${data.pluginId}-${data.panelId} ${operationDisplay}: ${duration}ms${slowWarning}` + ); + }; + + // Query-level events + onQueryStart = (data: performanceUtils.QueryPerformanceData): void => { + const queryStartMark = PERFORMANCE_MARKS.QUERY_START(data.origin, data.queryId); + createPerformanceMark(queryStartMark, data.timestamp); + }; + + onQueryComplete = (data: performanceUtils.QueryPerformanceData): void => { + const queryEndMark = PERFORMANCE_MARKS.QUERY_END(data.origin, data.queryId); + const queryStartMark = PERFORMANCE_MARKS.QUERY_START(data.origin, data.queryId); + const queryMeasureName = PERFORMANCE_MEASURES.QUERY(data.origin, data.queryId); + + createPerformanceMark(queryEndMark, data.timestamp); + createPerformanceMeasure(queryMeasureName, queryStartMark, queryEndMark); + + const duration = (data.duration || 0).toFixed(1); + const slowWarning = (data.duration || 0) > SLOW_OPERATION_THRESHOLD_MS ? ' ⚠️ SLOW' : ''; + + const queryType = data.queryType.replace(/^(getDataSource\/|AnnotationsDataLayer\/)/, ''); // Remove prefixes + writePerformanceLog('SPL', `[QUERY ${data.origin}] ${queryType} [${data.queryId}]: ${duration}ms${slowWarning}`); + }; + + private createStandardizedPanelMark(data: performanceUtils.PanelPerformanceData, phase: 'start' | 'end'): void { + const { operation, panelKey, operationId } = data; + + switch (operation) { + case 'query': + const markName = + phase === 'start' + ? PERFORMANCE_MARKS.PANEL_QUERY_START(panelKey, operationId) + : PERFORMANCE_MARKS.PANEL_QUERY_END(panelKey, operationId); + createPerformanceMark(markName, data.timestamp); + break; + + case 'plugin-load': + const pluginMarkName = + phase === 'start' + ? PERFORMANCE_MARKS.PANEL_PLUGIN_LOAD_START(panelKey, operationId) + : PERFORMANCE_MARKS.PANEL_PLUGIN_LOAD_END(panelKey, operationId); + createPerformanceMark(pluginMarkName, data.timestamp); + break; + + case 'fieldConfig': + const fieldConfigMarkName = + phase === 'start' + ? PERFORMANCE_MARKS.PANEL_FIELD_CONFIG_START(panelKey, operationId) + : PERFORMANCE_MARKS.PANEL_FIELD_CONFIG_END(panelKey, operationId); + createPerformanceMark(fieldConfigMarkName, data.timestamp); + break; + + case 'render': + const renderMarkName = + phase === 'start' + ? PERFORMANCE_MARKS.PANEL_RENDER_START(panelKey, operationId) + : PERFORMANCE_MARKS.PANEL_RENDER_END(panelKey, operationId); + createPerformanceMark(renderMarkName, data.timestamp); + break; + + case 'transform': + const transformationId = data.metadata.transformationId; + if (phase === 'start') { + createPerformanceMark( + PERFORMANCE_MARKS.PANEL_TRANSFORM_START(panelKey, transformationId, operationId), + data.timestamp + ); + } else { + const isError = data.metadata.error || data.metadata.success === false; + const transformEndMarkName = isError + ? PERFORMANCE_MARKS.PANEL_TRANSFORM_ERROR(panelKey, transformationId, operationId) + : PERFORMANCE_MARKS.PANEL_TRANSFORM_END(panelKey, transformationId, operationId); + createPerformanceMark(transformEndMarkName, data.timestamp); + } + break; + + default: + break; + } + } + + private createStandardizedPanelMeasure(data: performanceUtils.PanelPerformanceData): void { + const { operation, panelKey, operationId } = data; + + switch (operation) { + case 'query': + const startMark = PERFORMANCE_MARKS.PANEL_QUERY_START(panelKey, operationId); + const endMark = PERFORMANCE_MARKS.PANEL_QUERY_END(panelKey, operationId); + const measureName = PERFORMANCE_MEASURES.PANEL_QUERY(panelKey, operationId); + createPerformanceMeasure(measureName, startMark, endMark); + break; + + case 'plugin-load': + const pluginStartMark = PERFORMANCE_MARKS.PANEL_PLUGIN_LOAD_START(panelKey, operationId); + const pluginEndMark = PERFORMANCE_MARKS.PANEL_PLUGIN_LOAD_END(panelKey, operationId); + const pluginMeasureName = PERFORMANCE_MEASURES.PANEL_PLUGIN_LOAD(panelKey, operationId); + createPerformanceMeasure(pluginMeasureName, pluginStartMark, pluginEndMark); + break; + + case 'fieldConfig': + const fieldConfigStartMark = PERFORMANCE_MARKS.PANEL_FIELD_CONFIG_START(panelKey, operationId); + const fieldConfigEndMark = PERFORMANCE_MARKS.PANEL_FIELD_CONFIG_END(panelKey, operationId); + const fieldConfigMeasureName = PERFORMANCE_MEASURES.PANEL_FIELD_CONFIG(panelKey, operationId); + createPerformanceMeasure(fieldConfigMeasureName, fieldConfigStartMark, fieldConfigEndMark); + break; + + case 'render': + const renderStartMark = PERFORMANCE_MARKS.PANEL_RENDER_START(panelKey, operationId); + const renderEndMark = PERFORMANCE_MARKS.PANEL_RENDER_END(panelKey, operationId); + const renderMeasureName = PERFORMANCE_MEASURES.PANEL_RENDER(panelKey, operationId); + createPerformanceMeasure(renderMeasureName, renderStartMark, renderEndMark); + break; + + case 'transform': + const transformationId = data.metadata.transformationId; + const transformStartMark = PERFORMANCE_MARKS.PANEL_TRANSFORM_START(panelKey, transformationId, operationId); + + const isError = data.metadata.error || data.metadata.success === false; + const transformEndMark = isError + ? PERFORMANCE_MARKS.PANEL_TRANSFORM_ERROR(panelKey, transformationId, operationId) + : PERFORMANCE_MARKS.PANEL_TRANSFORM_END(panelKey, transformationId, operationId); + + const transformMeasureName = PERFORMANCE_MEASURES.PANEL_TRANSFORM(panelKey, transformationId, operationId); + createPerformanceMeasure(transformMeasureName, transformStartMark, transformEndMark); + break; + + default: + break; + } + } +} + +// Global singleton instance with lazy initialization +let scenePerformanceLogger: ScenePerformanceLogger | null = null; + +export function initializeScenePerformanceLogger(): ScenePerformanceLogger { + if (!scenePerformanceLogger) { + scenePerformanceLogger = new ScenePerformanceLogger(); + scenePerformanceLogger.initialize(); + + // Register as global performance observer + registerPerformanceObserver(scenePerformanceLogger, 'SPL'); + } + return scenePerformanceLogger; +} + +export function getScenePerformanceLogger(): ScenePerformanceLogger { + return initializeScenePerformanceLogger(); +} diff --git a/public/app/features/dashboard/services/dashboard-render-performance-profiling.md b/public/app/features/dashboard/services/dashboard-render-performance-profiling.md index 80f6fc522f6..cbef48d7411 100644 --- a/public/app/features/dashboard/services/dashboard-render-performance-profiling.md +++ b/public/app/features/dashboard/services/dashboard-render-performance-profiling.md @@ -10,25 +10,34 @@ This documentation describes the dashboard render performance metrics exposed fr - [Tracked Interactions](#tracked-interactions) - [Core Performance-Tracked Interactions](#core-performance-tracked-interactions) - [Interaction Origin Mapping](#interaction-origin-mapping) +- [Panel-Level Performance Attribution](#panel-level-performance-attribution) + - [Overview](#panel-level-overview) + - [Panel Operations Tracked](#panel-operations-tracked) + - [Performance Observer Architecture](#performance-observer-architecture) - [Profiling Implementation](#profiling-implementation) - [Profile Data Structure](#profile-data-structure) - [Collected Metrics](#collected-metrics) -- [Debugging and Development](#debugging-and-development) - - [Enable Profiler Debug Logging](#enable-profiler-debug-logging) - - [Enable Echo Service Debug Logging](#enable-echo-service-debug-logging) - - [Browser Performance Profiler](#browser-performance-profiler) -- [Analytics Integration](#analytics-integration) - - [Interaction Reporting](#interaction-reporting) - - [Data Collection](#data-collection) -- [Implementation Details](#implementation-details) - [Long Frame Detection](#long-frame-detection) +- [Analytics Integration](#analytics-integration) + - [Analytics Components](#analytics-components) + - [Chrome DevTools Integration](#chrome-devtools-integration) + - [Data Collection](#data-collection) +- [Debugging and Development](#debugging-and-development) + - [Enable Performance Debug Logging](#enable-performance-debug-logging) + - [Console Output Examples](#console-output-examples) + - [Browser Performance Profiler](#browser-performance-profiler) +- [Implementation Details](#implementation-details) + - [Architecture Overview](#architecture-overview) - [Tab Inactivity Handling](#tab-inactivity-handling) + - [Profile Isolation](#profile-isolation) - [Related Documentation](#related-documentation) ## Overview The exposed dashboard performance metrics feature provides comprehensive tracking and profiling of dashboard interactions, allowing administrators and developers to analyze dashboard render performance, user interactions, and identify performance bottlenecks. +The system includes **panel-level performance attribution** through an observer pattern architecture, providing visibility into individual panel operations within dashboard interactions. This enables identification of performance bottlenecks at both dashboard and panel levels, with comprehensive analytics reporting and Chrome DevTools integration. + ## Configuration ### Enabling Performance Metrics @@ -110,6 +119,102 @@ logMeasurement(`dashboard_render`, payload, { interactionType: e.origin, dashboa The profiling system uses profiler event's `origin` directly as the `interactionType`, providing direct mapping between user actions and performance measurements. +## Panel-Level Performance Attribution + +### Panel-Level Overview + +The panel-level performance attribution system uses a observer pattern architecture built around `ScenePerformanceTracker` to provide comprehensive visibility into individual panel operations. When dashboard profiling is enabled, `VizPanelRenderProfiler` instances are automatically attached to all panels, providing granular tracking of panel lifecycle operations. + +**Key Features:** + +- **Complete lifecycle tracking**: Monitors plugin load, query execution, data transformation, field configuration, and rendering phases +- **Sub-millisecond precision timing**: Chrome DevTools integration via performance marks and measurements +- **Operation ID correlation**: UUID-based operation IDs with crypto fallback for cross-environment compatibility +- **Observer pattern architecture**: Clean separation between performance tracking and business logic with extensible observer support +- **Real-time analytics aggregation**: Structured data format ready for analytics reporting +- **Conditional profiling**: Analytics aggregator only initialized when profiling is enabled +- **Type-safe interfaces**: Comprehensive TypeScript support with event-specific interfaces + +### Panel Operations Tracked + +The system tracks the following panel operations: + +| Operation | Description | When Tracked | +| ------------- | --------------------- | ----------------------------------------------- | +| `plugin-load` | Plugin initialization | When panel plugin is loaded | +| `query` | Data source queries | When panel executes queries | +| `transform` | Data transformations | When data is transformed (SceneDataTransformer) | +| `fieldConfig` | Field configuration | When field configurations are applied | +| `render` | Panel rendering | When panel is rendered | + +Each operation is tracked with: + +- **Operation ID**: UUID-based unique identifier for correlating start/complete events (e.g., `query-a1b2c3d4-e5f6-7890-abcd-ef1234567890`) +- **Timing**: High-precision start and end timestamps with sub-millisecond duration calculation +- **Metadata**: Operation-specific data (query types, transformation IDs, plugin information, etc.) + +### Operation ID Format + +The system generates unique operation IDs using a standardized format: + +``` +- +``` + +**Examples:** + +- `plugin-load-550e8400-e29b-41d4-a716-446655440000` +- `query-a1b2c3d4-e5f6-7890-abcd-ef1234567890` +- `transform-b2c3d4e5-f6g7-8901-bcde-f23456789012` +- `fieldConfig-c3d4e5f6-g7h8-9012-cdef-345678901234` +- `render-d4e5f6g7-h8i9-0123-def0-456789012345` + +**Benefits:** + +- **Global Uniqueness**: UUIDs prevent ID collisions across dashboard sessions +- **Cross-Environment Compatibility**: Crypto fallback ensures operation in all environments +- **Operation Correlation**: Enables precise tracking of start/complete event pairs +- **Debugging**: Human-readable prefixes make log analysis easier + +### Performance Observer Architecture + +The system uses `ScenePerformanceTracker` as a centralized coordinator that manages performance observers through an event-driven architecture. The performance utilities are organized under the `performanceUtils` namespace. + +```typescript +// Import performance utilities from scenes +import { performanceUtils } from '@grafana/scenes'; + +// Observer interface implemented by analytics components +interface ScenePerformanceObserver { + onDashboardInteractionStart?(data: performanceUtils.DashboardInteractionStartData): void; + onDashboardInteractionMilestone?(data: performanceUtils.DashboardInteractionMilestoneData): void; + onDashboardInteractionComplete?(data: performanceUtils.DashboardInteractionCompleteData): void; + onPanelOperationStart?(data: performanceUtils.PanelPerformanceData): void; + onPanelOperationComplete?(data: performanceUtils.PanelPerformanceData): void; + onQueryStart?(data: performanceUtils.QueryPerformanceData): void; + onQueryComplete?(data: performanceUtils.QueryPerformanceData): void; +} + +// Register observers with the performance tracker +const tracker = performanceUtils.getScenePerformanceTracker(); +tracker.addObserver(myObserver); +``` + +**Operation ID Generation:** + +The system generates unique operation IDs for correlating start/complete events using UUID with fallback support: + +```typescript +// Uses crypto.randomUUID() when available, Math.random() fallback for compatibility +const operationId = performanceUtils.generateOperationId('panel-query'); +// Result: "panel-query-550e8400-e29b-41d4-a716-446655440000" +``` + +**Registered Observers:** + +- **`DashboardAnalyticsAggregator`**: Aggregates panel metrics for analytics reporting (conditionally initialized) +- **`ScenePerformanceLogger`**: Creates Chrome DevTools performance marks and console logs + ## Profiling Implementation ### Profile Data Structure @@ -153,42 +258,274 @@ The performance metrics provide detailed insights into where time is spent durin - **Total Duration (`duration`)**: Complete time from interaction start to completion - **Network Time (`networkDuration`)**: Time spent waiting for server responses (data source queries, API calls) - **Processing Time (`processingTime`)**: Time spent on client-side operations (rendering, computations, DOM updates) -- **Long Frames (`longFramesCount` & `longFramesTotalTime`)**: Frames exceeding 50ms threshold indicate potential UI jank or performance issues. These metrics help identify interactions causing poor user experience: - - `longFramesCount`: The number of frames that exceeded the 50ms threshold - - `longFramesTotalTime`: The total accumulated time of all long frames, indicating the severity of performance issues - - **Detection Method**: Automatically uses Long Animation Frame API when available (Chrome 123+), falls back to manual tracking for broader browser support +- **Long Frames (`longFramesCount` & `longFramesTotalTime`)**: Frames exceeding 50ms threshold indicate potential UI jank or performance issues + +### Long Frame Detection + +The profiler includes sophisticated long frame detection using the Long Animation Frame (LoAF) API when available, with automatic fallback to manual frame tracking: + +#### Detection Methods + +1. **Long Animation Frame API (Primary)** + - **Browser Support**: Chrome 123+ (automatically detected) + - **Threshold**: 50ms (standard LoAF threshold) + - **Benefits**: Browser-level accuracy, script attribution, automatic buffering control + +2. **Manual Frame Tracking (Fallback)** + - **Browser Support**: All browsers + - **Threshold**: 50ms (same as LoAF) + - **Implementation**: Uses requestAnimationFrame for frame monitoring + +#### Metrics Collected + +- **`longFramesCount`**: Number of frames exceeding the 50ms threshold +- **`longFramesTotalTime`**: Cumulative duration of all long frames during interaction + +This helps identify: + +- Rendering performance issues impacting user experience +- Interactions causing UI jank or frame drops +- Performance optimization opportunities + +## Analytics Integration + +### Analytics Components + +The performance tracking system integrates with Grafana's analytics through two main components: + +#### DashboardAnalyticsAggregator + +Aggregates panel-level performance metrics for analytics reporting: + +- Collects and aggregates metrics for all panel operations +- Tracks operation counts and total time spent per panel +- Sends comprehensive analytics reports via `reportInteraction` and `logMeasurement` +- Provides detailed panel breakdowns including slow panel detection + +#### ScenePerformanceLogger + +Creates Chrome DevTools performance marks and measurements for debugging: + +- Generates performance marks for all dashboard and panel operations +- Creates performance measurements for timing visualization +- Provides console logging for real-time debugging +- Integrates with Chrome DevTools Performance timeline + +### Chrome DevTools Integration + +Performance operations are recorded as marks and measurements in the Chrome DevTools Performance timeline: + +**Dashboard-level marks:** + +``` +Dashboard Interaction Start: +Dashboard Interaction End: +Dashboard Milestone: : +``` + +**Panel-level marks:** + +``` +Panel Query Start: : +Panel Query End: : +Panel Render Start: : +Panel Render End: : +``` + +### Data Collection + +The system collects and reports data at two levels: + +#### Dashboard Interaction Data + +Reported for each interaction via `reportInteraction` and `logMeasurement`: + +```typescript +{ + interactionType: string, // Type of interaction + uid: string, // Dashboard UID + duration: number, // Total duration + networkDuration: number, // Network time + processingTime: number, // Client-side processing time + startTs: number, // Profile start timestamp + endTs: number, // Profile end timestamp + longFramesCount: number, // Number of long frames + longFramesTotalTime: number, // Total time of long frames + totalJSHeapSize: number, // Memory metrics + usedJSHeapSize: number, + jsHeapSizeLimit: number, + timeSinceBoot: number // Time since frontend boot +} +``` + +#### Panel-Level Metrics + +Aggregated by `DashboardAnalyticsAggregator` for each panel with detailed operation tracking: + +```typescript +{ + panelId: string, // Panel identifier + pluginId: string, // Plugin type (e.g., 'timeseries', 'stat') + pluginVersion?: string, // Plugin version + totalQueryTime: number, // Total time spent in queries + totalTransformationTime: number, // Total time in transformations + totalRenderTime: number, // Total render time + totalFieldConfigTime: number, // Total field config time + pluginLoadTime: number, // Plugin initialization time + + // Individual operations with UUID-based operation IDs + pluginLoadOperations: Array<{ + operationId: string, // e.g., "plugin-load-550e8400-e29b-41d4-a716-446655440000" + duration: number, + timestamp: number + }>, + queryOperations: Array<{ // Individual query operations + operationId: string, // e.g., "query-a1b2c3d4-e5f6-7890-abcd-ef1234567890" + duration: number, + timestamp: number, + queryType?: string + }>, + transformationOperations: Array<{ + duration: number, + timestamp: number, + transformationType?: string + }>, + fieldConfigOperations: Array<{ + duration: number, + timestamp: number + }>, + renderOperations: Array<{ // Individual render operations + duration: number, + timestamp: number + }>, + + // Performance analysis + isSlowPanel: boolean, // true if total time > SLOW_OPERATION_THRESHOLD_MS (500ms) + slowOperationThreshold: number, // Current threshold value (500ms) + totalPanelTime: number // Sum of all operation times +} +``` ## Debugging and Development -### Enable Profiler Debug Logging +### Enable Performance Debug Logging -To observe profiling events in the browser console: +To observe performance profiling events in the browser console: ```javascript -localStorage.setItem('grafana.debug.scenes', 'true'); +// Enable performance debug logging +localStorage.setItem('grafana.debug.sceneProfiling', 'true'); ``` -#### Console Output +### Performance Threshold Configuration -When debug logging is enabled, you'll see console logs for each profiling event: +The system uses a const threshold to identify slow operations: + +- **Default Threshold**: `SLOW_OPERATION_THRESHOLD_MS = 500` milliseconds +- **Applies to**: Individual panel operations and total panel performance +- **Slow Panel Detection**: Panels exceeding threshold display ⚠️ warnings in logs +- **Analytics Integration**: Slow panel count included in dashboard analytics reports + +**Example Slow Operation Warning:** + +```javascript +SPL: [PANEL] timeseries-panel-1 query [query-abc123]: 125.3ms ⚠️ SLOW +DAA: 🎨 Panel timeseries-panel-1: 125.3ms total ⚠️ SLOW +``` + +### Console Output Examples + +With debug logging enabled, you'll see detailed performance logs: + +#### Dashboard Interaction Logs ``` -SceneRenderProfiler: Profile started[clean] - ├─ Origin: dashboard_view - └─ Timestamp: 1072.5ms -LongFrameDetector: Started tracking with LoAF API method, threshold: 50ms -... // intermediate steps adding profile crumbs -LongFrameDetector: Long frame detected (LoAF): 67.4ms at 1071.5ms -LongFrameDetector: Long frame detected (LoAF): 76.3ms at 1139.8ms -... // more long frame detections -SceneRenderProfiler: Profile completed - ├─ Timestamp: 3530.6ms - ├─ Total time: 156.8ms - ├─ Slow frames: 16.3ms (1 frames) - └─ Long frames: 143.7ms (2 frames) -SceneRenderProfiler: Stopped long frame detection - profile complete at 3530.6ms +SRP: [PROFILER] dashboard_view started (clean) +LFD: Started tracking with LoAF API method, threshold: 50ms +SPL: [DASHBOARD] dashboard_view started: My Dashboard +SRP: [PROFILER] dashboard_view completed + ├─ Duration: 156.8ms + ├─ Long frames: 143.7ms (2 frames) + └─ Network time: 45.2ms ``` +#### Panel Operation Logs + +``` +SPL: [PANEL] timeseries-panel-1 plugin-load: 39.0ms +SPL: [PANEL] timeseries-panel-1 query [query-a1b2c3d4-e5f6-7890-abcd]: 45.2ms +SPL: [PANEL] timeseries-panel-1 transform: 12.3ms +SPL: [PANEL] timeseries-panel-1 fieldConfig: 5.0ms +SPL: [PANEL] timeseries-panel-1 render: 23.8ms ⚠️ SLOW +``` + +#### VizPanelRenderProfiler Logs + +The `VizPanelRenderProfiler` provides lifecycle and error logging (only visible with scenes debug logging enabled): + +``` +VizPanelRenderProfiler [My Dashboard Panel]: Plugin changed to timeseries +VizPanelRenderProfiler [My Dashboard Panel]: Cleaned up +VizPanelRenderProfiler: Not attached to a VizPanel +VizPanelRenderProfiler: Panel has no key, skipping tracking +``` + +#### Analytics Aggregator Summary + +The `DashboardAnalyticsAggregator` creates structured **collapsible console groups** for detailed analysis. Each panel gets its own expandable group in the browser console: + +``` +DAA: [ANALYTICS] dashboard_view | 4 panels analyzed | 1 slow panels ⚠️ + DAA: 📊 Dashboard (ms): { + duration: 156.8, + network: 45.2, + interactionType: "dashboard_view", + slowPanels: 1 + } + DAA: 📈 Analytics payload: { /* comprehensive analytics data */ } + + // Per-panel detailed breakdown (console group for each panel) + DAA: 🎨 Panel timeseries-panel-1: 125.3ms total ⚠️ SLOW + DAA: 🔧 Plugin: { + id: "timeseries", + version: "10.0.0", + panelId: "panel-1", + panelKey: "panel-1" + } + DAA: ⚡ Performance (ms): { + totalTime: 125.3, + isSlowPanel: true, + breakdown: { + query: 45.2, + transform: 12.3, + render: 23.8, + fieldConfig: 5.0, + pluginLoad: 39.0 + } + } + DAA: 📊 Queries: { + count: 2, + details: [ + { operation: 1, duration: 25.1, timestamp: 1729692845100.123 }, + { operation: 2, duration: 20.1, timestamp: 1729692845125.456 } + ] + } + DAA: 🔄 Transformations: { + count: 1, + details: [ + { operation: 1, duration: 12.3, timestamp: 1729692845150.789 } + ] + } + DAA: 🎨 Renders: { + count: 1, + details: [ + { operation: 1, duration: 23.8, timestamp: 1729692845163.012 } + ] + } +``` + +**Note**: The indentation shows the **console group hierarchy**. In the browser console, each panel creates a collapsible group that can be expanded to see detailed operation breakdowns. The main dashboard analytics group contains nested panel groups for organized analysis. + ### Enable Echo Service Debug Logging To observe Echo events in the browser console: @@ -207,99 +544,79 @@ When Echo debug logging is enabled, you'll see console logs for each profiling e ### Browser Performance Profiler -Dashboard interactions can be recorded in the browser's performance profiler, where they appear as: +Dashboard and panel operations are recorded in the Chrome DevTools Performance timeline with detailed marks and measurements: + +**Dashboard Marks:** ``` -Dashboard Interaction +Dashboard Interaction Start: dashboard-550e8400-e29b-41d4-a716-446655440000 +Dashboard Interaction End: dashboard-550e8400-e29b-41d4-a716-446655440000 +Dashboard Milestone: dashboard-550e8400-e29b-41d4-a716-446655440000:queries_complete +Dashboard Milestone: dashboard-550e8400-e29b-41d4-a716-446655440000:actual_interaction_complete ``` -## Analytics Integration +**Panel Marks:** -### Interaction Reporting - -Performance data is integrated with Grafana's analytics system through: - -- **`reportInteraction`**: Reports interaction events to Echo service with performance data -- **`logMeasurement`**: Records Faro's performance measurements with metadata - -### Data Collection - -The system reports the following data for each interaction: - -```typescript -{ - interactionType: string, // Type of interaction - uid: string, // Dashboard UID - duration: number, // Total duration - networkDuration: number, // Network time - processingTime: number, // Client-side processing time (duration - networkDuration) - startTs: number, // Profile start timestamp - endTs: number, // Profile end timestamp - totalJSHeapSize: number, // Memory metrics - usedJSHeapSize: number, - jsHeapSizeLimit: number, - longFramesCount: number, // Number of long frames (>50ms threshold) - longFramesTotalTime: number, // Total time of all long frames - timeSinceBoot: number // Time since frontend boot -} ``` +Panel Plugin Load Start: panel-1:plugin-load-a1b2c3d4-e5f6-7890-abcd-ef1234567890 +Panel Plugin Load End: panel-1:plugin-load-a1b2c3d4-e5f6-7890-abcd-ef1234567890 +Panel Query Start: panel-1:query-b2c3d4e5-f6g7-8901-bcde-f23456789012 +Panel Query End: panel-1:query-b2c3d4e5-f6g7-8901-bcde-f23456789012 +Panel Transform Start: panel-1:transform-c3d4e5f6-g7h8-9012-cdef-345678901234 +Panel Transform End: panel-1:transform-c3d4e5f6-g7h8-9012-cdef-345678901234 +Panel Field Config Start: panel-1:fieldConfig-d4e5f6g7-h8i9-0123-def0-456789012345 +Panel Field Config End: panel-1:fieldConfig-d4e5f6g7-h8i9-0123-def0-456789012345 +Panel Render Start: panel-1:render-e5f6g7h8-i9j0-1234-ef01-567890123456 +Panel Render End: panel-1:render-e5f6g7h8-i9j0-1234-ef01-567890123456 +``` + +These marks enable visual timeline analysis of: + +- Overall dashboard interaction timing +- Individual panel operation performance +- Parallel vs sequential operations +- Performance bottlenecks ## Implementation Details -The profiler is integrated into dashboard creation paths and uses a singleton pattern to share profiler instances across dashboard reloads. The performance tracking is implemented using the `SceneRenderProfiler` from the `@grafana/scenes` library. +### Architecture Overview -### Long Frame Detection +The performance tracking system consists of multiple integrated components with observer pattern architecture: -The profiler uses the Long Animation Frame (LoAF) API when available to monitor frame rendering performance during dashboard interactions: +1. **SceneRenderProfiler** (Scenes library - `performanceUtils` namespace) + - Singleton profiler instance shared across dashboard reloads + - Tracks dashboard interactions and manages long frame detection + - Integrates with VizPanelRenderProfiler for comprehensive panel-level tracking -#### Primary Method: Long Animation Frame API +2. **ScenePerformanceTracker** (Scenes library - `performanceUtils` namespace) + - Central coordinator implementing observer pattern architecture + - Distributes performance events to registered observers without coupling + - Provides type-safe interfaces for different event types + - Supports extensible observer registration with clean separation of concerns -- **Browser Support**: Chrome 123+ (automatically detected) -- **Threshold**: 50ms (standard LoAF threshold) -- **Benefits**: - - Browser-level accuracy and performance - - Standards-based implementation - - More efficient than manual tracking - - Automatic buffering control for real-time detection +3. **VizPanelRenderProfiler** (Scenes library - `performanceUtils` namespace) + - Automatically attached to individual panels when profiling is enabled + - Tracks complete panel lifecycle: plugin-load, query, transform, fieldConfig, render + - Uses UUID-based operation IDs with crypto fallback for cross-environment compatibility + - Reports structured performance data to ScenePerformanceTracker -#### Fallback Method: Manual Frame Tracking +4. **DashboardAnalyticsAggregator** (Grafana) + - **Conditionally initialized**: Only activated when `enableProfiling` is true + - Aggregates panel metrics for analytics reporting with slow panel detection + - Uses configurable threshold (SLOW_OPERATION_THRESHOLD_MS = 100ms) + - Sends comprehensive reports via reportInteraction and logMeasurement -- **Browser Support**: All browsers -- **Threshold**: 50ms (same as LoAF threshold) -- **Used when**: LoAF API is not available -- **Implementation**: Uses requestAnimationFrame for frame monitoring - -Both methods track: - -- **Count**: Number of frames exceeding the threshold -- **Total Time**: Cumulative duration of all long frames - -#### Debug Output - -With LoAF API: - -``` -LongFrameDetector: Long frame detected (LoAF): 67.4ms at 1234.5ms -``` - -With manual fallback: - -``` -LongFrameDetector: Long frame detected (manual): 38.2ms (threshold: 50ms) -``` - -This metric is particularly valuable for: - -- Detecting rendering performance issues that impact user experience -- Identifying when interactions cause UI jank or frame drops -- Measuring the impact of performance optimizations on frame rendering -- Comparing performance across different browsers and environments +5. **ScenePerformanceLogger** (Grafana) + - Creates Chrome DevTools performance marks and measurements + - Provides structured console logging for debugging with localStorage controls + - Maps operations to standardized performance mark names + - Integrates with browser Performance Timeline API ### Tab Inactivity Handling To prevent meaningless profiling data when users switch browser tabs, the `SceneRenderProfiler` implements dual protection mechanisms: -#### Primary Protection: Page Visibility API +#### Page Visibility API The profiler automatically cancels active profiling sessions when the browser tab becomes inactive: @@ -313,109 +630,71 @@ document.addEventListener('visibilitychange', () => { This provides immediate response to tab switches using the browser's native visibility change events. -#### Fallback Protection: Frame Length Detection +#### Frame Length Measurement for Performance Analysis -As a backup mechanism, the profiler detects tab inactivity by monitoring frame duration: +The profiler measures frame lengths during the post-interaction recording window for performance analysis: ```javascript -if (frameLength > TAB_INACTIVE_THRESHOLD) { - // 1000ms - this.cancelProfile(); - return; -} +const frameLength = currentFrameTime - lastFrameTime; +this.#recordedTrailingSpans.push(frameLength); ``` -This fallback catches cases where visibility events might be missed and prevents recording of artificially long frame times (hours instead of milliseconds) that occur when `requestAnimationFrame` callbacks resume after tab reactivation. +**Note**: Frame length measurement is used for performance analytics only. The profiler does **not** use frame length thresholds for tab inactivity detection. Tab inactivity protection relies exclusively on the Page Visibility API for accurate and immediate response to tab changes. -### Profile Isolation and Overlapping Interactions +### Profile Isolation -To ensure accurate performance measurements, the `SceneRenderProfiler` implements profile isolation to handle rapid user interactions: +To ensure accurate performance measurements, the profiler implements automatic profile cancellation when handling rapid user interactions: -#### Understanding Trailing Frame Recording +**Trailing Frame Recording**: After an interaction completes, the profiler continues recording for 2 seconds (POST_STORM_WINDOW) to capture delayed rendering effects. -After the main interaction completes, the profiler continues to record "trailing frames" for 2 seconds (POST_STORM_WINDOW) to capture any delayed rendering effects. This ensures complete performance measurement including: - -- Delayed DOM updates -- Asynchronous rendering operations -- Secondary effects from the initial interaction - -#### Problem: Mixed Performance Data - -When users perform rapid interactions during this 2-second trailing frame window (e.g., quickly changing time ranges or triggering a refresh), the performance data from multiple actions could be mixed into a single profile. This led to: - -- Inaccurate performance measurements -- Profile events that never completed -- Crumbs from different interactions being combined -- Trailing frames from one interaction being attributed to another - -#### Solution: Automatic Profile Cancellation - -Starting with `@grafana/scenes` v6.30.4, the profiler automatically cancels the current profile when a new interaction begins while trailing frames are still being recorded: +**Automatic Cancellation**: When a new interaction begins during trailing frame recording, the current profile is cancelled to prevent mixing performance data: ```javascript -// When new profile is requested while still recording trailing frames if (this.#trailAnimationFrameId) { this.cancelProfile(); - this._startNewProfile(name, true); // true = forced profile -} else { - this.addCrumb(name); + this._startNewProfile(name, true); // forced profile } ``` -This ensures: +**Profile Types**: -- Each interaction gets its own isolated measurement -- No mixing of performance data between different user actions -- Clean separation of interaction metrics +- **Clean Start**: No active profile when starting +- **Forced Start**: Previous profile cancelled for new interaction -#### Profile Start Types - -The profiler now distinguishes between two types of profile starts: - -1. **Clean Start**: Profile started when no other profile is active -2. **Forced Start (Interrupted)**: Profile started by cancelling a previous active profile - -This information is logged in debug mode: - -``` -SceneRenderProfiler: Profile started[forced]: {origin: "refresh", crumbs: []} -SceneRenderProfiler: Profile started[clean]: {origin: "dashboard_view", crumbs: []} -``` - -Additionally, when a profile is cancelled due to overlapping interactions: - -``` -SceneRenderProfiler: Cancelled recording frames, new profile started -``` - -#### Example Scenario - -1. User changes time range (profile starts) -2. Dashboard finishes loading after 500ms (main profile complete) -3. Profiler continues recording trailing frames to capture delayed effects -4. At 1 second, user clicks refresh button -5. Without this fix: Refresh would be added as a crumb to the time range profile -6. With this fix: Time range profile is cancelled, new refresh profile starts cleanly - -This fix is particularly important for dashboards with: - -- Auto-refresh enabled -- Slow API responses -- Rapid user interactions - -Without profile isolation, these scenarios could result in profiles that never complete and mix data from multiple unrelated interactions. +This ensures each interaction gets isolated measurements, preventing data contamination from overlapping operations. ## Related Documentation -- [PR #858 - Add SceneRenderProfiler to scenes](https://github.com/grafana/scenes/pull/858) -- [PR #99629 - Dashboard render performance metrics](https://github.com/grafana/grafana/pull/99629) -- [PR #108658 - Dashboard: Tweak interaction tracking](https://github.com/grafana/grafana/pull/108658) -- [PR #1195 - Enhance SceneRenderProfiler with additional interaction tracking](https://github.com/grafana/scenes/pull/1195) -- [PR #1198 - Make SceneRenderProfiler optional and injectable](https://github.com/grafana/scenes/pull/1198) -- [PR #1199 - SceneRenderProfiler: add start and end timestamps to profile events](https://github.com/grafana/scenes/pull/1199) -- [PR #1205 - SceneRenderProfiler: Handle tab inactivity](https://github.com/grafana/scenes/pull/1205) -- [PR #1209 - SceneRenderProfiler: Only capture network requests within measurement window](https://github.com/grafana/scenes/pull/1209) -- [PR #1211 - SceneRenderProfiler: Improve profiler accuracy by adding cancellation and skipping inactive tabs](https://github.com/grafana/scenes/pull/1211) -- [PR #1212 - SceneQueryController: Fix profiler query controller registration on scene re-activation](https://github.com/grafana/scenes/pull/1212) -- [PR #1225 - SceneRenderProfiler: Handle overlapping profiles by cancelling previous profile](https://github.com/grafana/scenes/pull/1225) -- [PR #1235 - Implement long frame detection with LoAF API and manual fallback](https://github.com/grafana/scenes/pull/1235) +### Foundational Performance System + +- [PR #858 - Add SceneRenderProfiler to scenes](https://github.com/grafana/scenes/pull/858) ✅ Merged +- [PR #99629 - Dashboard render performance metrics](https://github.com/grafana/grafana/pull/99629) ✅ Merged +- [PR #108658 - Dashboard: Tweak interaction tracking](https://github.com/grafana/grafana/pull/108658) ✅ Merged + +### Enhanced Profiling Features + +- [PR #1195 - Enhance SceneRenderProfiler with additional interaction tracking](https://github.com/grafana/scenes/pull/1195) ✅ Merged +- [PR #1198 - Make SceneRenderProfiler optional and injectable](https://github.com/grafana/scenes/pull/1198) ✅ Merged +- [PR #1199 - SceneRenderProfiler: add start and end timestamps to profile events](https://github.com/grafana/scenes/pull/1199) ✅ Merged +- [PR #1205 - SceneRenderProfiler: Handle tab inactivity](https://github.com/grafana/scenes/pull/1205) ✅ Merged +- [PR #1209 - SceneRenderProfiler: Only capture network requests within measurement window](https://github.com/grafana/scenes/pull/1209) ✅ Merged +- [PR #1211 - SceneRenderProfiler: Improve profiler accuracy by adding cancellation and skipping inactive tabs](https://github.com/grafana/scenes/pull/1211) ✅ Merged +- [PR #1212 - SceneQueryController: Fix profiler query controller registration on scene re-activation](https://github.com/grafana/scenes/pull/1212) ✅ Merged +- [PR #1225 - SceneRenderProfiler: Handle overlapping profiles by cancelling previous profile](https://github.com/grafana/scenes/pull/1225) ✅ Merged +- [PR #1235 - Implement long frame detection with LoAF API and manual fallback](https://github.com/grafana/scenes/pull/1235) ✅ Merged + +### Panel-Level Performance Attribution System + +- [PR #1265 - Panel-level performance attribution system](https://github.com/grafana/scenes/pull/1265) 🔄 **In Review** + - Modern observer pattern architecture with ScenePerformanceTracker + - Complete panel lifecycle tracking (plugin-load, query, transform, fieldConfig, render) + - UUID-based operation IDs with crypto fallback for cross-environment compatibility + - performanceUtils namespace organization for clean API separation + - Type-safe performance interfaces with comprehensive TypeScript support + - Chrome DevTools integration via performance marks and measurements +- [PR #112137 - Dashboard performance analytics system with Scenes integration](https://github.com/grafana/grafana/pull/112137) 🔄 **In Review** + - DashboardAnalyticsAggregator with conditional initialization + - ScenePerformanceLogger for debugging and Chrome DevTools integration + - Configurable performance thresholds (SLOW_OPERATION_THRESHOLD_MS) + - Comprehensive analytics reporting via reportInteraction and logMeasurement + - Integration with the panel-level performance attribution system from PR #1265 diff --git a/public/app/features/dashboard/services/performanceConstants.ts b/public/app/features/dashboard/services/performanceConstants.ts new file mode 100644 index 00000000000..c5ae3474d33 --- /dev/null +++ b/public/app/features/dashboard/services/performanceConstants.ts @@ -0,0 +1,82 @@ +// Standardized performance mark names for Scene operations +export const PERFORMANCE_MARKS = { + // Panel operations + PANEL_QUERY_START: (panelKey: string, operationId?: string) => + operationId ? `scenes.panel.query.start.${panelKey}.${operationId}` : `scenes.panel.query.start.${panelKey}`, + PANEL_QUERY_END: (panelKey: string, operationId?: string) => + operationId ? `scenes.panel.query.end.${panelKey}.${operationId}` : `scenes.panel.query.end.${panelKey}`, + PANEL_PLUGIN_LOAD_START: (panelKey: string, operationId?: string) => + operationId + ? `scenes.panel.pluginLoad.start.${panelKey}.${operationId}` + : `scenes.panel.pluginLoad.start.${panelKey}`, + PANEL_PLUGIN_LOAD_END: (panelKey: string, operationId?: string) => + operationId ? `scenes.panel.pluginLoad.end.${panelKey}.${operationId}` : `scenes.panel.pluginLoad.end.${panelKey}`, + PANEL_FIELD_CONFIG_START: (panelKey: string, operationId?: string) => + operationId + ? `scenes.panel.fieldConfig.start.${panelKey}.${operationId}` + : `scenes.panel.fieldConfig.start.${panelKey}`, + PANEL_FIELD_CONFIG_END: (panelKey: string, operationId?: string) => + operationId + ? `scenes.panel.fieldConfig.end.${panelKey}.${operationId}` + : `scenes.panel.fieldConfig.end.${panelKey}`, + PANEL_RENDER_START: (panelKey: string, operationId?: string) => + operationId ? `scenes.panel.render.start.${panelKey}.${operationId}` : `scenes.panel.render.start.${panelKey}`, + PANEL_RENDER_END: (panelKey: string, operationId?: string) => + operationId ? `scenes.panel.render.end.${panelKey}.${operationId}` : `scenes.panel.render.end.${panelKey}`, + PANEL_TRANSFORM_START: (panelKey: string, transformationId: string, operationId?: string) => + operationId + ? `scenes.panel.transform.start.${panelKey}.${transformationId}.${operationId}` + : `scenes.panel.transform.start.${panelKey}.${transformationId}`, + PANEL_TRANSFORM_END: (panelKey: string, transformationId: string, operationId?: string) => + operationId + ? `scenes.panel.transform.end.${panelKey}.${transformationId}.${operationId}` + : `scenes.panel.transform.end.${panelKey}.${transformationId}`, + PANEL_TRANSFORM_ERROR: (panelKey: string, transformationId: string, operationId?: string) => + operationId + ? `scenes.panel.transform.error.${panelKey}.${transformationId}.${operationId}` + : `scenes.panel.transform.error.${panelKey}.${transformationId}`, + + // Dashboard operations + DASHBOARD_INTERACTION_START: (operationId: string) => `scenes.dashboard.interaction.start.${operationId}`, + DASHBOARD_INTERACTION_END: (operationId: string) => `scenes.dashboard.interaction.end.${operationId}`, + DASHBOARD_MILESTONE: (operationId: string, milestone: string) => + `scenes.dashboard.milestone.${milestone}.${operationId}`, + + // Query operations + QUERY_START: (panelId: string, queryId: string) => `scenes.query.start.${panelId}.${queryId}`, + QUERY_END: (panelId: string, queryId: string) => `scenes.query.end.${panelId}.${queryId}`, +}; + +// Standardized performance measure names +export const PERFORMANCE_MEASURES = { + // Panel operations + PANEL_QUERY: (panelKey: string, operationId?: string) => + operationId ? `scenes.panel.query.duration.${panelKey}.${operationId}` : `scenes.panel.query.duration.${panelKey}`, + PANEL_PLUGIN_LOAD: (panelKey: string, operationId?: string) => + operationId + ? `scenes.panel.pluginLoad.duration.${panelKey}.${operationId}` + : `scenes.panel.pluginLoad.duration.${panelKey}`, + PANEL_FIELD_CONFIG: (panelKey: string, operationId?: string) => + operationId + ? `scenes.panel.fieldConfig.duration.${panelKey}.${operationId}` + : `scenes.panel.fieldConfig.duration.${panelKey}`, + PANEL_RENDER: (panelKey: string, operationId?: string) => + operationId + ? `scenes.panel.render.duration.${panelKey}.${operationId}` + : `scenes.panel.render.duration.${panelKey}`, + PANEL_TRANSFORM: (panelKey: string, transformationId: string, operationId?: string) => + operationId + ? `scenes.panel.transform.duration.${panelKey}.${transformationId}.${operationId}` + : `scenes.panel.transform.duration.${panelKey}.${transformationId}`, + + // Dashboard operations + DASHBOARD_INTERACTION: (operationId: string) => `scenes.dashboard.interaction.duration.${operationId}`, + + // Query operations + QUERY: (panelId: string, queryId: string) => `scenes.query.duration.${panelId}.${queryId}`, +}; + +/** + * Threshold in milliseconds for determining slow operations (panels, queries, transformations, etc.) + */ +export const SLOW_OPERATION_THRESHOLD_MS = 500; diff --git a/public/app/features/dashboard/services/performanceUtils.ts b/public/app/features/dashboard/services/performanceUtils.ts new file mode 100644 index 00000000000..a09096b513c --- /dev/null +++ b/public/app/features/dashboard/services/performanceUtils.ts @@ -0,0 +1,139 @@ +import { store } from '@grafana/data'; +import { performanceUtils, writePerformanceLog } from '@grafana/scenes'; + +/** + * Utility function to register a performance observer with the global tracker + * Reduces duplication between ScenePerformanceLogger and DashboardAnalyticsAggregator + */ +export function registerPerformanceObserver( + observer: performanceUtils.ScenePerformanceObserver, + loggerName: string +): void { + const tracker = performanceUtils.getScenePerformanceTracker(); + tracker.addObserver(observer); + + writePerformanceLog(loggerName, 'Initialized globally and registered as performance observer'); +} + +/** + * Chrome-specific performance.memory interface (non-standard) + */ +export interface PerformanceMemory { + totalJSHeapSize: number; + usedJSHeapSize: number; + jsHeapSizeLimit: number; +} + +/** + * Extended Performance interface with Chrome's memory property + */ +export interface PerformanceWithMemory extends Performance { + memory?: PerformanceMemory; +} + +/** + * Type guard to check if performance has memory property (Chrome-specific) + */ +function hasPerformanceMemory(perf: Performance): perf is PerformanceWithMemory { + return 'memory' in perf; +} + +/** + * Safely get performance memory metrics (Chrome-specific, non-standard) + * Returns zero values for browsers without performance.memory support + */ +export function getPerformanceMemory(): PerformanceMemory { + if (hasPerformanceMemory(performance)) { + return { + totalJSHeapSize: performance.memory?.totalJSHeapSize || 0, + usedJSHeapSize: performance.memory?.usedJSHeapSize || 0, + jsHeapSizeLimit: performance.memory?.jsHeapSizeLimit || 0, + }; + } + + // Fallback for browsers without performance.memory + return { + totalJSHeapSize: 0, + usedJSHeapSize: 0, + jsHeapSizeLimit: 0, + }; +} + +/** + * Check if performance logging is enabled via localStorage + */ +function isPerformanceLoggingEnabled(): boolean { + if (typeof window !== 'undefined') { + return store.get('grafana.debug.sceneProfiling') === 'true'; + } + return false; +} + +/** + * Write a collapsible performance log group (follows writePerformanceLog pattern) + */ +export function writePerformanceGroupStart(logger: string, message: string): void { + if (isPerformanceLoggingEnabled()) { + // eslint-disable-next-line no-console + console.groupCollapsed(`${logger}: ${message}`); + } +} + +/** + * Write a performance log within a group (follows writePerformanceLog pattern) + */ +export function writePerformanceGroupLog(logger: string, message: string, data?: unknown): void { + if (isPerformanceLoggingEnabled()) { + if (data) { + // eslint-disable-next-line no-console + console.log(message, data); + } else { + // eslint-disable-next-line no-console + console.log(message); + } + } +} + +/** + * End a performance log group (follows writePerformanceLog pattern) + */ +export function writePerformanceGroupEnd(): void { + if (isPerformanceLoggingEnabled()) { + // eslint-disable-next-line no-console + console.groupEnd(); + } +} + +/** + * Safely creates a performance mark, ignoring errors if the Performance API is not available. + */ +export function createPerformanceMark(name: string, timestamp?: number): void { + try { + if (typeof performance !== 'undefined' && performance.mark) { + if (timestamp !== undefined) { + performance.mark(name, { startTime: timestamp }); + } else { + performance.mark(name); + } + } + } catch (error) { + console.error(`❌ Failed to create performance mark: ${name}`, { timestamp, error }); + } +} + +/** + * Safely creates a performance measure, ignoring errors if the Performance API is not available. + */ +export function createPerformanceMeasure(name: string, startMark: string, endMark?: string): void { + try { + if (typeof performance !== 'undefined' && performance.measure) { + if (endMark) { + performance.measure(name, startMark, endMark); + } else { + performance.measure(name, startMark); + } + } + } catch (error) { + console.error(`❌ Failed to create performance measure: ${name}`, { startMark, endMark, error }); + } +} diff --git a/public/app/features/scopes/selector/ScopesSelectorService.ts b/public/app/features/scopes/selector/ScopesSelectorService.ts index 370258d4785..b7c502de5bd 100644 --- a/public/app/features/scopes/selector/ScopesSelectorService.ts +++ b/public/app/features/scopes/selector/ScopesSelectorService.ts @@ -1,6 +1,6 @@ import { Scope, ScopeNode, store as storeImpl } from '@grafana/data'; import { config, locationService } from '@grafana/runtime'; -import { SceneRenderProfiler } from '@grafana/scenes'; +import { performanceUtils } from '@grafana/scenes'; import { getDashboardSceneProfiler } from 'app/features/dashboard/services/DashboardProfiler'; import { ScopesApiClient } from '../ScopesApiClient'; @@ -54,7 +54,8 @@ export class ScopesSelectorService extends ScopesServiceBase { let dashboardReloadSpy: jest.SpyInstance; beforeEach(() => { jest.spyOn(console, 'log').mockImplementation(); + jest.spyOn(console, 'warn').mockImplementation(); }); beforeAll(() => { config.featureToggles.scopeFilters = true; diff --git a/yarn.lock b/yarn.lock index 792fd086e36..12c677dba68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2324,15 +2324,15 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.3.6": - version: 2.3.6 - resolution: "@formatjs/ecma402-abstract@npm:2.3.6" +"@formatjs/ecma402-abstract@npm:2.3.4": + version: 2.3.4 + resolution: "@formatjs/ecma402-abstract@npm:2.3.4" dependencies: "@formatjs/fast-memoize": "npm:2.2.7" - "@formatjs/intl-localematcher": "npm:0.6.2" + "@formatjs/intl-localematcher": "npm:0.6.1" decimal.js: "npm:^10.4.3" tslib: "npm:^2.8.0" - checksum: 10/30b1b5cd6b62ba46245f934429936592df5500bc1b089dc92dd49c826757b873dd92c305dcfe370701e4df6b057bf007782113abb9b65db550d73be4961718bc + checksum: 10/573971ffc291096a4b9fcc80b4708124e89bf2e3ac50e0f78b41eb797e9aa1b842f4dc3665e4467a853c738386821769d9e40408a1d25bc73323a1f057a16cf2 languageName: node linkType: hard @@ -2376,13 +2376,13 @@ __metadata: linkType: hard "@formatjs/intl-durationformat@npm:^0.7.0": - version: 0.7.6 - resolution: "@formatjs/intl-durationformat@npm:0.7.6" + version: 0.7.4 + resolution: "@formatjs/intl-durationformat@npm:0.7.4" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.6" - "@formatjs/intl-localematcher": "npm:0.6.2" + "@formatjs/ecma402-abstract": "npm:2.3.4" + "@formatjs/intl-localematcher": "npm:0.6.1" tslib: "npm:^2.8.0" - checksum: 10/442236ba85bcd9cb7296c43a708271fa09f110b1ca9d5899066d00812fc2965eaeaec6b5240be421b80daba62860352131088449ba0fcd2061f671cec6240f0b + checksum: 10/d62273ecd635475ca91e9b501301f3f396403fa91b584c550734b19b2d194ba1316b27303fed985c1d42ae933d54eb220da6540edfdf376b0d9371ecfd0d4e15 languageName: node linkType: hard @@ -2395,12 +2395,12 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.6.2": - version: 0.6.2 - resolution: "@formatjs/intl-localematcher@npm:0.6.2" +"@formatjs/intl-localematcher@npm:0.6.1": + version: 0.6.1 + resolution: "@formatjs/intl-localematcher@npm:0.6.1" dependencies: tslib: "npm:^2.8.0" - checksum: 10/eb12a7f5367bbecdfafc20d7f005559ce840f420e970f425c5213d35e94e86dfe75bde03464971a26494bf8427d4961269db22ecad2834f2a19d888b5d9cc064 + checksum: 10/c7b3bc8395d18670677f207b2fd107561fff5d6394a9b4273c29e0bea920300ec3a2eefead600ebb7761c04a770cada28f78ac059f84d00520bfb57a9db36998 languageName: node linkType: hard @@ -3555,11 +3555,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:^6.40.1": - version: 6.40.1 - resolution: "@grafana/scenes-react@npm:6.40.1" +"@grafana/scenes-react@npm:^6.41.0": + version: 6.42.0 + resolution: "@grafana/scenes-react@npm:6.42.0" dependencies: - "@grafana/scenes": "npm:6.40.1" + "@grafana/scenes": "npm:6.42.0" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3571,7 +3571,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/4515d53609d7b49e234bbc5c3da8131406c20b8ac0e54b1a5ca3ce3d1d42220ff08db65d31c83c9796188ef9d76e9a24fef170badb793a56b44bbabd31302575 + checksum: 10/05db719566e8499b2f9f46ac7da83b2c669bc29d3169b42cb800ad0bb099ad7d5dead4109975d5c65bb711a98965f4c8eb20ea3e60d8f46c958be0508346f84e languageName: node linkType: hard @@ -3601,9 +3601,9 @@ __metadata: languageName: node linkType: hard -"@grafana/scenes@npm:6.40.1, @grafana/scenes@npm:^6.40.1": - version: 6.40.1 - resolution: "@grafana/scenes@npm:6.40.1" +"@grafana/scenes@npm:6.42.0, @grafana/scenes@npm:^6.41.0": + version: 6.42.0 + resolution: "@grafana/scenes@npm:6.42.0" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3623,7 +3623,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/f3f33d58bca9d05cf8d9527564e2f6bd9f164b44531382972c2b1ed0a54f54e29a6130fcab5797cd5dbba673e5d678ae5d1ef9c01ce6fe7895ee0f0c9408bc10 + checksum: 10/790afb142b6aa78a8a4f1359ec363e5a6c6c198873eafd39398b125d65881fb37514cf0c2f424e020dedca53a11a378f1dc87bab8ebc69fa76f5d10d59837e9f languageName: node linkType: hard @@ -6029,9 +6029,9 @@ __metadata: linkType: hard "@openfeature/core@npm:^1.9.0": - version: 1.9.1 - resolution: "@openfeature/core@npm:1.9.1" - checksum: 10/6099e16b1b4cd6e3c45c05ab4acd44c9cb4ab501b676ab6f3e77f0be1b56abc7506c5629187381333a02975d315d831e0905582435b356d9676255e72a465899 + version: 1.9.0 + resolution: "@openfeature/core@npm:1.9.0" + checksum: 10/c6d20edc09053afd99752fe46d8328158680950bca4b86679f67f79249d7226eea127b31fffdc38e26ecb729f2bab5a4a5a7c1db708ae76b7fbbac68cd56f094 languageName: node linkType: hard @@ -6056,11 +6056,11 @@ __metadata: linkType: hard "@openfeature/web-sdk@npm:^1.6.1": - version: 1.6.2 - resolution: "@openfeature/web-sdk@npm:1.6.2" + version: 1.6.1 + resolution: "@openfeature/web-sdk@npm:1.6.1" peerDependencies: "@openfeature/core": ^1.9.0 - checksum: 10/0fcc0ef76ff51d4725a00ff07755b21941b647f775d1ff04fc3d973143e78c909ccba485f582fcbc7f6201f42c795c85bcb103c43d64924fedf13ea517f625ac + checksum: 10/8bd7d1ea386e21cdd7492cab2fd1d2b138b4e6a376a4c0a40244633e5955f6452039bc2633fc5230bd7b494506a4137ba7210d40850634f9618f77a0ee435f9d languageName: node linkType: hard @@ -18832,8 +18832,8 @@ __metadata: "@grafana/plugin-ui": "npm:^0.10.10" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:^6.40.1" - "@grafana/scenes-react": "npm:^6.40.1" + "@grafana/scenes": "npm:^6.41.0" + "@grafana/scenes-react": "npm:^6.41.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/test-utils": "workspace:*" @@ -23060,9 +23060,9 @@ __metadata: linkType: hard "lossless-json@npm:^4.1.1": - version: 4.3.0 - resolution: "lossless-json@npm:4.3.0" - checksum: 10/a984a882c79b6e62a917d0202518472c17587bdee002f1427d7ec61115f9fbdeb5f0baa9f0e9fff8d9dbacd17da6b6c910012b2dcab69dd33d151cb7c13d5a37 + version: 4.2.0 + resolution: "lossless-json@npm:4.2.0" + checksum: 10/b29cbf9a90b8f3108219ff410223a90a3a6ad14cf902eed4038571d9421c69ade7077fb59737e1ca20f3f404ddd68fad69be11365b0d79b4b61421b054759551 languageName: node linkType: hard @@ -23530,9 +23530,9 @@ __metadata: linkType: hard "micro-memoize@npm:^4.1.2": - version: 4.2.0 - resolution: "micro-memoize@npm:4.2.0" - checksum: 10/260e27a5c15809f7dc435a89a424d93e49ccca62b743fa96ee72f254264df58c95edacbd847a2899a6d7f8ad0daef188548a8b12add5ccff83e88869a50a762c + version: 4.1.3 + resolution: "micro-memoize@npm:4.1.3" + checksum: 10/4e9c7767911cc76ae9c9779584ec87844437af9446b295a01774640a732c2c7f91944794027f44625031f7330ab7f9147740d0a9fb612680d1d2d858dad43402 languageName: node linkType: hard @@ -24230,11 +24230,11 @@ __metadata: linkType: hard "nanoid@npm:^5.0.9": - version: 5.1.6 - resolution: "nanoid@npm:5.1.6" + version: 5.1.5 + resolution: "nanoid@npm:5.1.5" bin: nanoid: bin/nanoid.js - checksum: 10/4109dbcf596d7f297a9b42f459b8f01694a03ebbdd2f41408d963ad54e5ec7234cbe7b4acad137751f31add11bb4fb3415a3e688082516745812811f05570014 + checksum: 10/6de2d006b51c983be385ef7ee285f7f2a57bd96f8c0ca881c4111461644bd81fafc2544f8e07cb834ca0f3e0f3f676c1fe78052183f008b0809efe6e273119f5 languageName: node linkType: hard From 31a2d2aff41c8884882a0e5409706b8cdbf7b298 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Thu, 30 Oct 2025 09:20:41 +0200 Subject: [PATCH 34/72] Provisioning: Show last and preview branches in the branch dropdown (#113148) * Provisioning: Show configured and last used branches * Remove unused var * Add hooks * Extract branch logic * remove type assertion * fix tests * Memoize descriptions --- eslint-suppressions.json | 5 -- .../provisioning/File/FileStatusPage.tsx | 22 +++-- .../DeleteProvisionedDashboardForm.tsx | 2 + .../MoveProvisionedDashboardForm.tsx | 2 + .../SaveProvisionedDashboardForm.test.tsx | 12 +++ .../SaveProvisionedDashboardForm.tsx | 11 +-- .../Folders/DeleteProvisionedFolderForm.tsx | 2 + .../Folders/NewProvisionedFolderForm.test.tsx | 12 +++ .../Folders/NewProvisionedFolderForm.tsx | 1 + .../ResourceEditFormSharedFields.test.tsx | 12 +++ .../Shared/ResourceEditFormSharedFields.tsx | 39 +++------ .../hooks/useBranchDropdownOptions.ts | 84 +++++++++++++++++++ .../provisioning/hooks/useLastBranch.ts | 35 ++++++++ .../provisioning/hooks/usePRBranch.ts | 13 +++ .../hooks/useProvisionedRequestHandler.ts | 27 +++++- public/locales/en-US/grafana.json | 4 +- 16 files changed, 232 insertions(+), 51 deletions(-) create mode 100644 public/app/features/provisioning/hooks/useBranchDropdownOptions.ts create mode 100644 public/app/features/provisioning/hooks/useLastBranch.ts create mode 100644 public/app/features/provisioning/hooks/usePRBranch.ts diff --git a/eslint-suppressions.json b/eslint-suppressions.json index c704beaefc3..2d84e04dadf 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -3374,11 +3374,6 @@ "count": 2 } }, - "public/app/features/provisioning/File/FileStatusPage.tsx": { - "@typescript-eslint/consistent-type-assertions": { - "count": 2 - } - }, "public/app/features/provisioning/Shared/BranchValidationError.tsx": { "react/no-unescaped-entities": { "count": 26 diff --git a/public/app/features/provisioning/File/FileStatusPage.tsx b/public/app/features/provisioning/File/FileStatusPage.tsx index 5a946122e11..9b32521a5fb 100644 --- a/public/app/features/provisioning/File/FileStatusPage.tsx +++ b/public/app/features/provisioning/File/FileStatusPage.tsx @@ -18,12 +18,24 @@ import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { PROVISIONING_URL } from '../constants'; import { useGetResourceRepositoryView } from '../hooks/useGetResourceRepositoryView'; +import { usePRBranch } from '../hooks/usePRBranch'; + +enum TabSelection { + File = 'file', + Existing = 'existing', + DryRun = 'dryRun', +} + +function isTabSelection(value: unknown): value is TabSelection { + return value === TabSelection.File || value === TabSelection.Existing || value === TabSelection.DryRun; +} export default function FileStatusPage() { const params = useParams(); const [queryParams] = useQueryParams(); - const ref = (queryParams['ref'] as string) ?? undefined; - const tab = (queryParams['tab'] as TabSelection) ?? TabSelection.File; + const ref = usePRBranch(); + const tabParam = queryParams['tab']; + const tab = isTabSelection(tabParam) ? tabParam : TabSelection.File; const name = params['name'] ?? ''; const path = params['*'] ?? ''; const file = useGetRepositoryFilesWithPathQuery({ name, path, ref }); @@ -52,12 +64,6 @@ export default function FileStatusPage() { ); } -enum TabSelection { - File = 'file', - Existing = 'existing', - DryRun = 'dryRun', -} - interface Props { wrap: ResourceWrapper; repo: string; diff --git a/public/app/features/provisioning/components/Dashboards/DeleteProvisionedDashboardForm.tsx b/public/app/features/provisioning/components/Dashboards/DeleteProvisionedDashboardForm.tsx index 743975947a0..87948f5f555 100644 --- a/public/app/features/provisioning/components/Dashboards/DeleteProvisionedDashboardForm.tsx +++ b/public/app/features/provisioning/components/Dashboards/DeleteProvisionedDashboardForm.tsx @@ -166,6 +166,8 @@ export function DeleteProvisionedDashboardForm({ request, workflow, resourceType: 'dashboard', + repository, + selectedBranch: ref || loadedFromRef, successMessage: t( 'dashboard-scene.delete-provisioned-dashboard-form.success-message', 'Dashboard deleted successfully' diff --git a/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx b/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx index 637d0861882..abdd4a74eae 100644 --- a/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx +++ b/public/app/features/provisioning/components/Dashboards/MoveProvisionedDashboardForm.tsx @@ -225,6 +225,8 @@ export function MoveProvisionedDashboardForm({ request: moveRequest, workflow, resourceType: 'dashboard', + repository, + selectedBranch: ref || loadedFromRef, successMessage: t( 'dashboard-scene.move-provisioned-dashboard-form.success-message', 'Dashboard moved successfully' diff --git a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.test.tsx b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.test.tsx index 0f3029c51df..f74f7ddd949 100644 --- a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.test.tsx +++ b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.test.tsx @@ -77,6 +77,18 @@ jest.mock('app/api/clients/provisioning/v0alpha1', () => ({ useGetRepositoryRefsQuery: jest.fn().mockReturnValue({ data: { items: [] }, isLoading: false, error: null }), })); +// Mock the new hooks that depend on router context +jest.mock('../../hooks/usePRBranch', () => ({ + usePRBranch: jest.fn().mockReturnValue(undefined), +})); + +jest.mock('../../hooks/useLastBranch', () => ({ + useLastBranch: jest.fn().mockReturnValue({ + getLastBranch: jest.fn().mockReturnValue(undefined), + setLastBranch: jest.fn(), + }), +})); + jest.mock('app/features/dashboard-scene/saving/SaveDashboardForm', () => { const actual = jest.requireActual('app/features/dashboard-scene/saving/SaveDashboardForm'); return { diff --git a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx index bffaab79a73..a807ada4911 100644 --- a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx +++ b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx @@ -148,6 +148,7 @@ export function SaveProvisionedDashboardForm({ workflow, resourceType: 'dashboard', repository, + selectedBranch: methods.getValues().ref, handlers: { onBranchSuccess: ({ ref, path }, info, resource) => onBranchSuccess(ref, path, info, resource), onWriteSuccess, @@ -156,15 +157,7 @@ export function SaveProvisionedDashboardForm({ }); // Submit handler for saving the form data - const handleFormSubmit = async ({ - title, - description, - repo, - path, - comment, - ref, - folder, - }: ProvisionedDashboardFormData) => { + const handleFormSubmit = async ({ title, description, repo, path, comment, ref }: ProvisionedDashboardFormData) => { // Validate required fields if (!repo || !path) { console.error('Missing required fields for saving:', { repo, path }); diff --git a/public/app/features/provisioning/components/Folders/DeleteProvisionedFolderForm.tsx b/public/app/features/provisioning/components/Folders/DeleteProvisionedFolderForm.tsx index 102cacbf7d4..6682d21af88 100644 --- a/public/app/features/provisioning/components/Folders/DeleteProvisionedFolderForm.tsx +++ b/public/app/features/provisioning/components/Folders/DeleteProvisionedFolderForm.tsx @@ -143,6 +143,8 @@ function FormContent({ initialValues, parentFolder, repository, workflowOptions, request, workflow, resourceType: 'folder', + repository, + selectedBranch: ref, successMessage: t( 'browse-dashboards.delete-provisioned-folder-form.success-message', 'Folder deleted successfully' diff --git a/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.test.tsx b/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.test.tsx index 0bd80b282a9..1aa011c708c 100644 --- a/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.test.tsx +++ b/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.test.tsx @@ -51,6 +51,18 @@ jest.mock('app/api/clients/provisioning/v0alpha1', () => { }; }); +// Mock the new hooks that depend on router context +jest.mock('../../hooks/usePRBranch', () => ({ + usePRBranch: jest.fn().mockReturnValue(undefined), +})); + +jest.mock('../../hooks/useLastBranch', () => ({ + useLastBranch: jest.fn().mockReturnValue({ + getLastBranch: jest.fn().mockReturnValue(undefined), + setLastBranch: jest.fn(), + }), +})); + jest.mock('../../hooks/useProvisionedFolderFormData', () => { return { useProvisionedFolderFormData: jest.fn(), diff --git a/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx b/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx index 109277d38a7..367bcd25860 100644 --- a/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx +++ b/public/app/features/provisioning/components/Folders/NewProvisionedFolderForm.tsx @@ -89,6 +89,7 @@ function FormContent({ initialValues, repository, workflowOptions, folder, onDis workflow, repository, resourceType: 'folder', + selectedBranch: methods.getValues().ref, handlers: { onDismiss, onBranchSuccess, diff --git a/public/app/features/provisioning/components/Shared/ResourceEditFormSharedFields.test.tsx b/public/app/features/provisioning/components/Shared/ResourceEditFormSharedFields.test.tsx index 306571926c1..0df03fab5a4 100644 --- a/public/app/features/provisioning/components/Shared/ResourceEditFormSharedFields.test.tsx +++ b/public/app/features/provisioning/components/Shared/ResourceEditFormSharedFields.test.tsx @@ -13,6 +13,18 @@ jest.mock('app/api/clients/provisioning/v0alpha1', () => ({ useGetRepositoryRefsQuery: jest.fn().mockReturnValue({ data: { items: [] }, isLoading: false, error: null }), })); +// Mock the new hooks that depend on router context +jest.mock('../../hooks/usePRBranch', () => ({ + usePRBranch: jest.fn().mockReturnValue(undefined), +})); + +jest.mock('../../hooks/useLastBranch', () => ({ + useLastBranch: jest.fn().mockReturnValue({ + getLastBranch: jest.fn().mockReturnValue(undefined), + setLastBranch: jest.fn(), + }), +})); + const mockRepo: { github: RepositoryView; local: RepositoryView } = { github: { type: 'github', diff --git a/public/app/features/provisioning/components/Shared/ResourceEditFormSharedFields.tsx b/public/app/features/provisioning/components/Shared/ResourceEditFormSharedFields.tsx index da05af72fa2..ecad626dbba 100644 --- a/public/app/features/provisioning/components/Shared/ResourceEditFormSharedFields.tsx +++ b/public/app/features/provisioning/components/Shared/ResourceEditFormSharedFields.tsx @@ -10,6 +10,9 @@ import { WorkflowOption } from 'app/features/provisioning/types'; import { validateBranchName } from 'app/features/provisioning/utils/git'; import { isGitProvider } from 'app/features/provisioning/utils/repositoryTypes'; +import { useBranchDropdownOptions } from '../../hooks/useBranchDropdownOptions'; +import { useLastBranch } from '../../hooks/useLastBranch'; +import { usePRBranch } from '../../hooks/usePRBranch'; import { generateNewBranchName } from '../utils/newBranchName'; interface DashboardEditFormSharedFieldsProps { @@ -40,34 +43,16 @@ export const ResourceEditFormSharedFields = memo { - const options: Array<{ label: string; value: string; description?: string }> = []; + const { getLastBranch } = useLastBranch(); + const prBranch = usePRBranch(); + const lastBranch = getLastBranch(repository?.name); - const configuredBranch = repository?.branch; - const prefix = t( - 'provisioned-resource-form.save-or-delete-resource-shared-fields.suffix-configured-branch', - 'Configured branch' - ); - // Show the configured branch first in the list - if (configuredBranch) { - options.push({ - label: `${configuredBranch}`, - value: configuredBranch, - description: prefix, - }); - } - - // Create combobox options - if (branchData?.items) { - for (const ref of branchData.items) { - if (ref.name !== configuredBranch) { - options.push({ label: ref.name, value: ref.name }); - } - } - } - - return options; - }, [branchData?.items, repository?.branch]); + const branchOptions = useBranchDropdownOptions({ + repository, + prBranch, + lastBranch, + branchData, + }); const newBranchDefaultName = useMemo(() => generateNewBranchName(resourceType), [resourceType]); diff --git a/public/app/features/provisioning/hooks/useBranchDropdownOptions.ts b/public/app/features/provisioning/hooks/useBranchDropdownOptions.ts new file mode 100644 index 00000000000..4302cabf92b --- /dev/null +++ b/public/app/features/provisioning/hooks/useBranchDropdownOptions.ts @@ -0,0 +1,84 @@ +import { useMemo } from 'react'; + +import { t } from '@grafana/i18n'; +import { GetRepositoryRefsApiResponse, RepositoryView } from 'app/api/clients/provisioning/v0alpha1'; + +interface UseBranchDropdownOptionsParams { + repository?: RepositoryView; + prBranch?: string; + lastBranch?: string; + branchData?: GetRepositoryRefsApiResponse; +} + +interface BranchOption { + label: string; + value: string; + description?: string; +} + +function getBranchDescriptions() { + return { + configured: t( + 'provisioned-resource-form.save-or-delete-resource-shared-fields.suffix-configured-branch', + 'Configured branch' + ), + pr: t('provisioned-resource-form.save-or-delete-resource-shared-fields.suffix-pr-branch', 'Pull request branch'), + lastUsed: t('provisioned-resource-form.save-or-delete-resource-shared-fields.suffix-last-used', 'Last branch'), + }; +} + +/** + * Hook to generate branch dropdown options with proper ordering and deduplication. + * Order: Configured branch → PR branch → Last used branch → Other branches + */ +export const useBranchDropdownOptions = ({ + repository, + prBranch, + lastBranch, + branchData, +}: UseBranchDropdownOptionsParams): BranchOption[] => { + const descriptions = useMemo(() => getBranchDescriptions(), []); + + const options: BranchOption[] = []; + const addedBranches = new Set(); + + const configuredBranch = repository?.branch; + + if (configuredBranch) { + options.push({ + label: `${configuredBranch}`, + value: configuredBranch, + description: descriptions.configured, + }); + addedBranches.add(configuredBranch); + } + + if (prBranch && !addedBranches.has(prBranch)) { + options.push({ + label: prBranch, + value: prBranch, + description: descriptions.pr, + }); + addedBranches.add(prBranch); + } + + if (lastBranch && !addedBranches.has(lastBranch)) { + options.push({ + label: lastBranch, + value: lastBranch, + description: descriptions.lastUsed, + }); + addedBranches.add(lastBranch); + } + + if (branchData?.items) { + for (const ref of branchData.items) { + if (!addedBranches.has(ref.name)) { + options.push({ label: ref.name, value: ref.name }); + addedBranches.add(ref.name); + } + } + } + + return options; +}; diff --git a/public/app/features/provisioning/hooks/useLastBranch.ts b/public/app/features/provisioning/hooks/useLastBranch.ts new file mode 100644 index 00000000000..ca977362cc4 --- /dev/null +++ b/public/app/features/provisioning/hooks/useLastBranch.ts @@ -0,0 +1,35 @@ +import { useCallback } from 'react'; + +import { store } from '@grafana/data'; + +const LAST_BRANCH_KEY_PREFIX = 'grafana.provisioning.lastBranch'; + +/** + * Get the local storage key for a repository's last used branch + */ +const getStorageKey = (repositoryName: string) => { + return `${LAST_BRANCH_KEY_PREFIX}.${repositoryName}`; +}; + +/** + * Hook to manage the last used branch per repository in local storage + */ +export const useLastBranch = () => { + const getLastBranch = useCallback((repositoryName: string | undefined): string | undefined => { + if (!repositoryName) { + return undefined; + } + const key = getStorageKey(repositoryName); + return store.get(key) || undefined; + }, []); + + const setLastBranch = useCallback((repositoryName: string | undefined, branch: string | undefined) => { + if (!repositoryName || !branch) { + return; + } + const key = getStorageKey(repositoryName); + store.set(key, branch); + }, []); + + return { getLastBranch, setLastBranch }; +}; diff --git a/public/app/features/provisioning/hooks/usePRBranch.ts b/public/app/features/provisioning/hooks/usePRBranch.ts new file mode 100644 index 00000000000..ba31e94f132 --- /dev/null +++ b/public/app/features/provisioning/hooks/usePRBranch.ts @@ -0,0 +1,13 @@ +import { useQueryParams } from 'app/core/hooks/useQueryParams'; + +/** + * Hook to get a properly typed URL ref param + */ +export const usePRBranch = () => { + const [queryParams] = useQueryParams(); + const ref = queryParams['ref']; + if (typeof ref !== 'string') { + return undefined; + } + return ref; +}; diff --git a/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts b/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts index 188f6218950..528e3ee49be 100644 --- a/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts +++ b/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts @@ -14,6 +14,8 @@ import { refetchChildren } from 'app/features/browse-dashboards/state/actions'; import { RepoType } from 'app/features/provisioning/Wizard/types'; import { useDispatch } from 'app/types/store'; +import { useLastBranch } from './useLastBranch'; + type ResourceType = 'dashboard' | 'folder'; // Add more as needed, e.g., 'alert', etc. // Information object that gets passed to all handlers @@ -56,6 +58,7 @@ interface Props { successMessage?: string; repository?: RepositoryView; resourceType?: ResourceType; + selectedBranch?: string; // The branch selected by the user in the form } /** @@ -72,10 +75,12 @@ export function useProvisionedRequestHandler({ successMessage, repository, resourceType, + selectedBranch, }: Props) { const dispatch = useDispatch(); // useRef to ensure handlers are only called once per request const hasHandled = useRef(false); + const { setLastBranch } = useLastBranch(); useEffect(() => { const repoType = repository?.type || 'git'; @@ -97,6 +102,15 @@ export function useProvisionedRequestHandler({ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const resourceData = resource.upsert as Resource; + // Save the last used branch to local storage + if (workflow === 'branch' && ref) { + // For branch workflow, save the ref from the response + setLastBranch(repository?.name, ref); + } else if (workflow === 'write') { + // For write workflow, save the selectedBranch or fall back to repository branch + setLastBranch(repository?.name, selectedBranch || repository?.branch); + } + // Success message const message = successMessage || getContextualSuccessMessage(info); getAppEvents().publish({ @@ -121,7 +135,18 @@ export function useProvisionedRequestHandler({ handlers.onDismiss?.(); } - }, [request, workflow, handlers, successMessage, repository, resourceType, folderUID, dispatch]); + }, [ + request, + workflow, + handlers, + successMessage, + repository, + resourceType, + folderUID, + dispatch, + selectedBranch, + setLastBranch, + ]); } function getContextualSuccessMessage(info: ProvisionedOperationInfo): string { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 4e435b87ed2..62cf6c11a10 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11364,7 +11364,9 @@ "label-workflow": "Workflow", "placeholder-branch": "Select or enter branch name", "placeholder-new-branch": "Enter new branch name", - "suffix-configured-branch": "Configured branch" + "suffix-configured-branch": "Configured branch", + "suffix-last-used": "Last branch", + "suffix-pr-branch": "Pull request branch" } }, "provisioned-resource-preview-banner": { From 5dce711680759e775ba2eaf002417db0925a82d6 Mon Sep 17 00:00:00 2001 From: antonio <45235678+tonypowa@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:36:44 +0100 Subject: [PATCH 35/72] add feedback survey to contributing related docs (#113168) * add feedback survey to contributing related docs * removed deprecated drone logo and link * all pretty, no pity * removed indentation --- CONTRIBUTING.md | 4 +++- README.md | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c1f5d9ddace..c62e18c754e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,9 @@ Thank you for your interest in contributing to Grafana! We welcome all people who want to contribute in a healthy and constructive manner within our community. To help us create a safe and positive community experience for all, we require all participants to adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). -This document is a guide to help you through the process of contributing to Grafana. Be sure to check out the [Grafana Champions program](https://grafana.com/community/champions/?src=github&camp=community-cross-platform-engagement) as you start to contribute. It’s designed to recognize and empower individuals who are actively contributing to the growth and success of the Grafana ecosystem. +This document is a guide to help you through the process of contributing to Grafana. Be sure to check out the [Grafana Champions program](https://grafana.com/community/champions/?src=github&camp=community-cross-platform-engagement) as you start to contribute. It's designed to recognize and empower individuals who are actively contributing to the growth and success of the Grafana ecosystem. + +> **Help us improve!** We'd love to hear about your contributor experience. Take a moment to share your feedback in our [Open Source Contributor Experience Survey](https://gra.fan/ome). Your input helps us make contributing to Grafana better for everyone. Whether you're a new contributor or a seasoned veteran, we hope these resources help you connect with the community. diff --git a/README.md b/README.md index 033ebdc1ea6..d92844ed9aa 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ The open-source platform for monitoring and observability [![License](https://img.shields.io/github/license/grafana/grafana)](LICENSE) -[![Drone](https://drone.grafana.net/api/badges/grafana/grafana/status.svg)](https://drone.grafana.net/grafana/grafana) [![Go Report Card](https://goreportcard.com/badge/github.com/grafana/grafana)](https://goreportcard.com/report/github.com/grafana/grafana) Grafana allows you to query, visualize, alert on and understand your metrics no matter where they are stored. Create, explore, and share dashboards with your team and foster a data-driven culture: @@ -36,6 +35,8 @@ If you're interested in contributing to the Grafana project: - Explore our [beginner-friendly issues](https://github.com/grafana/grafana/issues?q=is%3Aopen+is%3Aissue+label%3A%22beginner+friendly%22). - Look through our [style guide and Storybook](https://developers.grafana.com/ui/latest/index.html). +> Share your contributor experience in our [feedback survey](https://gra.fan/ome) to help us improve. + ## Get involved - Follow [@grafana on X (formerly Twitter)](https://x.com/grafana/). From f9ef1b6bd0f9049ad007524589f3a2b2020adbd8 Mon Sep 17 00:00:00 2001 From: Levente Balogh Date: Thu, 30 Oct 2025 10:08:10 +0100 Subject: [PATCH 36/72] Switch variable: Stop allowing identical values (#113166) * fix: don't allow identical values for enabled and disabled states * fix: add missing translation --- .../components/SwitchVariableForm.test.tsx | 61 +++++++++++++++++++ .../components/SwitchVariableForm.tsx | 43 +++++++++++-- public/locales/en-US/grafana.json | 1 + 3 files changed, 101 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard-scene/settings/variables/components/SwitchVariableForm.test.tsx b/public/app/features/dashboard-scene/settings/variables/components/SwitchVariableForm.test.tsx index a5561a5ecd6..70c06cc2e35 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/SwitchVariableForm.test.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/SwitchVariableForm.test.tsx @@ -128,4 +128,65 @@ describe('SwitchVariableForm', () => { unmount(); }); }); + + it('should show error when enabled value matches disabled value', async () => { + const user = userEvent.setup(); + renderForm({ + enabledValue: 'on', + disabledValue: 'off', + }); + + const enabledInput = screen.getByTestId( + selectors.pages.Dashboard.Settings.Variables.Edit.SwitchVariable.enabledValueInput + ); + + await user.clear(enabledInput); + await user.type(enabledInput, 'off'); + + expect(screen.getByText('Enabled and disabled values cannot be the same')).toBeInTheDocument(); + expect(onEnabledValueChange).not.toHaveBeenCalledWith('off'); + }); + + it('should show error when disabled value matches enabled value', async () => { + const user = userEvent.setup(); + renderForm({ + enabledValue: 'on', + disabledValue: 'off', + }); + + const disabledInput = screen.getByTestId( + selectors.pages.Dashboard.Settings.Variables.Edit.SwitchVariable.disabledValueInput + ); + + await user.clear(disabledInput); + await user.type(disabledInput, 'on'); + + expect(screen.getByText('Enabled and disabled values cannot be the same')).toBeInTheDocument(); + expect(onDisabledValueChange).not.toHaveBeenCalledWith('on'); + }); + + it('should clear the error when the values are update to not be the same', async () => { + const user = userEvent.setup(); + renderForm({ + enabledValue: 'on', + disabledValue: 'off', + }); + + const enabledInput = screen.getByTestId( + selectors.pages.Dashboard.Settings.Variables.Edit.SwitchVariable.enabledValueInput + ); + const disabledInput = screen.getByTestId( + selectors.pages.Dashboard.Settings.Variables.Edit.SwitchVariable.disabledValueInput + ); + + // First make disabled value same as enabled + await user.clear(disabledInput); + await user.type(disabledInput, 'on'); + expect(screen.getByText('Enabled and disabled values cannot be the same')).toBeInTheDocument(); + + // Then change enabled value to something that is not identical + await user.clear(enabledInput); + await user.type(enabledInput, 'new'); + expect(screen.queryByText('Enabled and disabled values cannot be the same')).not.toBeInTheDocument(); + }); }); diff --git a/public/app/features/dashboard-scene/settings/variables/components/SwitchVariableForm.tsx b/public/app/features/dashboard-scene/settings/variables/components/SwitchVariableForm.tsx index a14824ab84e..2f00b67f161 100644 --- a/public/app/features/dashboard-scene/settings/variables/components/SwitchVariableForm.tsx +++ b/public/app/features/dashboard-scene/settings/variables/components/SwitchVariableForm.tsx @@ -28,12 +28,21 @@ export function SwitchVariableForm({ }: SwitchVariableFormProps) { const currentValuePairType = getCurrentValuePairType(enabledValue, disabledValue); const [isCustomValuePairType, setIsCustomValuePairType] = useState(currentValuePairType === 'custom'); + const [enabledValueInvalid, setEnabledValueInvalid] = useState(false); + const [disabledValueInvalid, setDisabledValueInvalid] = useState(false); + const identicalValuesErrorMessage = t( + 'dashboard-scene.switch-variable-form.same-values-error', + 'Enabled and disabled values cannot be the same' + ); const onValuePairTypeChange = (selection: ComboboxOption | null) => { if (!selection?.value) { return; } + setEnabledValueInvalid(false); + setDisabledValueInvalid(false); + switch (selection.value) { case 'boolean': onEnabledValueChange('true'); @@ -56,6 +65,28 @@ export function SwitchVariableForm({ } }; + const handleEnabledValueChange = (newEnabledValue: string) => { + const isInvalid = newEnabledValue === disabledValue; + + setEnabledValueInvalid(isInvalid); + setDisabledValueInvalid(false); + + if (!isInvalid) { + onEnabledValueChange(newEnabledValue); + } + }; + + const handleDisabledValueChange = (newDisabledValue: string) => { + const isInvalid = newDisabledValue === enabledValue; + + setDisabledValueInvalid(isInvalid); + setEnabledValueInvalid(false); + + if (!isInvalid) { + onDisabledValueChange(newDisabledValue); + } + }; + return ( <> @@ -90,12 +121,14 @@ export function SwitchVariableForm({ 'dashboard-scene.switch-variable-form.enabled-value-description', 'Value when switch is enabled' )} + error={enabledValueInvalid && identicalValuesErrorMessage} + invalid={enabledValueInvalid} > { - onEnabledValueChange(event.currentTarget.value); + handleEnabledValueChange(event.currentTarget.value); }} placeholder={t( 'dashboard-scene.switch-variable-form.enabled-value-placeholder', @@ -112,11 +145,13 @@ export function SwitchVariableForm({ 'dashboard-scene.switch-variable-form.disabled-value-description', 'Value when switch is disabled' )} + error={disabledValueInvalid && identicalValuesErrorMessage} + invalid={disabledValueInvalid} > onDisabledValueChange(event.currentTarget.value)} + defaultValue={disabledValue} + onChange={(event) => handleDisabledValueChange(event.currentTarget.value)} placeholder={t( 'dashboard-scene.switch-variable-form.disabled-value-placeholder', 'e.g. Off, Disabled, Inactive' diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 62cf6c11a10..dcbf60af7b5 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -6269,6 +6269,7 @@ "enabled-value": "Enabled value", "enabled-value-description": "Value when switch is enabled", "enabled-value-placeholder": "e.g. On, Enabled, Active", + "same-values-error": "Enabled and disabled values cannot be the same", "switch-options": "Switch options", "value-pair-type": "Value pair type", "value-pair-type-description": "Choose the type of values for the switch states" From 58098f93397ec2416fd7fed9c796d0e3876737ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Sencer=20=C3=96zcan?= <32759850+mustafasencer@users.noreply.github.com> Date: Thu, 30 Oct 2025 10:12:29 +0100 Subject: [PATCH 37/72] chore: improve unified dual writer logging (#113203) chore: improve dual writer logging --- pkg/storage/legacysql/dualwrite/dualwriter.go | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/pkg/storage/legacysql/dualwrite/dualwriter.go b/pkg/storage/legacysql/dualwrite/dualwriter.go index 72c05d5ed4d..cefaf923efc 100644 --- a/pkg/storage/legacysql/dualwrite/dualwriter.go +++ b/pkg/storage/legacysql/dualwrite/dualwriter.go @@ -37,6 +37,7 @@ type dualWriter struct { } func (d *dualWriter) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + log := logging.FromContext(ctx).With("method", "Get", "name", name) // If we read from unified, we can just do that and return. if d.readUnified { return d.unified.Get(ctx, name, options) @@ -44,6 +45,7 @@ func (d *dualWriter) Get(ctx context.Context, name string, options *metav1.GetOp // If legacy is still our main store, lets first read from it. legacyGet, err := d.legacy.Get(ctx, name, options) if err != nil { + log.Error("failed to get object from legacy storage", "err", err) return nil, err } // Once we have successfully read from legacy, we can check if we want to fail on a unified read. @@ -52,7 +54,6 @@ func (d *dualWriter) Get(ctx context.Context, name string, options *metav1.GetOp go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() if _, err := d.unified.Get(ctxBg, name, options); err != nil { - log := logging.FromContext(ctxBg).With("method", "Get") log.Error("failed background GET to unified", "err", err) } }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) @@ -61,6 +62,7 @@ func (d *dualWriter) Get(ctx context.Context, name string, options *metav1.GetOp // If it's not okay to fail, we have to check it in the foreground. _, unifiedErr := d.unified.Get(ctx, name, options) if unifiedErr != nil && !apierrors.IsNotFound(unifiedErr) { + log.Error("failed to get object from unified storage", "err", unifiedErr) return nil, unifiedErr } return legacyGet, nil @@ -71,7 +73,7 @@ func (d *dualWriter) List(ctx context.Context, options *metainternalversion.List var ( legacyOptions = options.DeepCopy() unifiedOptions = options.DeepCopy() - log = logging.FromContext(ctx).With("method", "List") + log = logging.FromContext(ctx).With("method", "List", "options", options) ) legacyToken, unifiedToken, err := parseContinueTokens(options.Continue) @@ -86,6 +88,7 @@ func (d *dualWriter) List(ctx context.Context, options *metainternalversion.List if d.readUnified { unifiedList, err := d.unified.List(ctx, unifiedOptions) if err != nil { + log.Error("failed to list objects from unified storage", "err", err) return nil, err } unifiedMeta, err := meta.ListAccessor(unifiedList) @@ -115,6 +118,7 @@ func (d *dualWriter) List(ctx context.Context, options *metainternalversion.List // If legacy is still the main store, lets first read from it. legacyList, err := d.legacy.List(ctx, legacyOptions) if err != nil { + log.Error("failed to list objects from legacy storage", "err", err) return nil, err } legacyMeta, err := meta.ListAccessor(legacyList) @@ -158,6 +162,7 @@ func (d *dualWriter) List(ctx context.Context, options *metainternalversion.List // If it's not okay to fail, we have to check it in the foreground. unifiedList, err := d.unified.List(ctx, unifiedOptions) if err != nil { + log.Error("failed to list objects from unified storage", "err", err) return nil, err } unifiedMeta, err := meta.ListAccessor(unifiedList) @@ -207,7 +212,7 @@ func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValida // will try to cleanup the object in legacy. createdFromLegacy, err := d.legacy.Create(ctx, in, createValidation, options) if err != nil { - log.Error("unable to create object in legacy storage", "err", err) + log.With("object", in).Error("failed to CREATE object in legacy storage", "err", err) return nil, err } @@ -234,11 +239,11 @@ func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValida if d.readUnified { storageObj, errObjectSt := d.unified.Create(ctx, createdCopy, createValidation, options) if errObjectSt != nil { - log.Error("unable to create object in unified storage", "err", errObjectSt) + log.With("object", createdCopy).Error("failed to CREATE object in unified storage", "err", errObjectSt) // If we cannot create in unified storage, attempt to clean up legacy. _, _, err = d.legacy.Delete(ctx, accCreated.GetName(), nil, &metav1.DeleteOptions{}) if err != nil { - log.Error("unable to cleanup object in legacy storage", "err", err) + log.With("name", accCreated.GetName()).Error("failed to CLEANUP object in legacy storage", "err", err) } return nil, errObjectSt } @@ -248,13 +253,13 @@ func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValida go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() if _, err := d.unified.Create(ctxBg, createdCopy, createValidation, options); err != nil { - log.Error("unable to create object in unified storage", "err", err) + log.With("object", createdCopy).Error("failed to CREATE object in unified storage", "err", err) } }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) } else { // Otherwise let's create it in the foreground and return any error. if _, err := d.unified.Create(ctx, createdCopy, createValidation, options); err != nil { - log.Error("unable to create object in unified storage", "err", err) + log.With("object", createdCopy).Error("failed to CREATE object in unified storage", "err", err) if d.errorIsOK { return createdFromLegacy, nil } @@ -262,7 +267,7 @@ func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValida // If we cannot create in unified storage, attempt to clean up legacy. _, _, errLegacy := d.legacy.Delete(ctx, accCreated.GetName(), nil, &metav1.DeleteOptions{}) if errLegacy != nil { - log.Error("unable to cleanup object in legacy storage", "err", errLegacy) + log.With("name", accCreated.GetName()).Error("failed to CLEANUP object in legacy storage", "err", errLegacy) } return nil, err } @@ -281,10 +286,12 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r // By setting RemovePermissions to false in the context, we will skip the deletion of permissions // in the legacy store. This is needed as otherwise the permissions would be missing when executing // the delete operation in the unified storage store. + log := logging.FromContext(ctx).With("method", "Delete", "name", name) ctx = utils.SetFolderRemovePermissions(ctx, false) objFromLegacy, asyncLegacy, err := d.legacy.Delete(ctx, name, deleteValidation, options) if err != nil && (!d.readUnified || !d.errorIsOK && !apierrors.IsNotFound(err)) { + log.Error("failed to DELETE object in legacy storage", "err", err) return nil, false, err } @@ -295,6 +302,7 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r if d.readUnified { objFromStorage, asyncStorage, err := d.unified.Delete(ctx, name, deleteValidation, options) if err != nil && !apierrors.IsNotFound(err) && !d.errorIsOK { + log.Error("failed to DELETE object in unified storage", "err", err) return nil, false, err } return objFromStorage, asyncStorage, nil @@ -304,7 +312,6 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r defer cancel() _, _, err := d.unified.Delete(ctxBg, name, deleteValidation, options) if err != nil && !apierrors.IsNotFound(err) && !d.errorIsOK { - log := logging.FromContext(ctxBg).With("method", "Delete") log.Error("failed background DELETE in unified storage", "err", err) } }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) @@ -312,6 +319,7 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r // Otherwise we just run it in the foreground and return an error if any might happen. _, _, err = d.unified.Delete(ctx, name, deleteValidation, options) if err != nil && !apierrors.IsNotFound(err) && !d.errorIsOK { + log.Error("failed to DELETE object in unified storage", "err", err) return nil, false, err } return objFromLegacy, asyncLegacy, nil @@ -319,7 +327,7 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r // Update overrides the behavior of the generic DualWriter and writes first to Storage and then to LegacyStorage. func (d *dualWriter) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - log := logging.FromContext(ctx).With("method", "Update") + log := logging.FromContext(ctx).With("method", "Update", "name", name, "objInfo", objInfo) // update in legacy first, and then unistore. Will return a failure if either fails. // @@ -341,7 +349,7 @@ func (d *dualWriter) Update(ctx context.Context, name string, objInfo rest.Updat objFromLegacy, createdLegacy, err := d.legacy.Update(ctx, name, legacyInfo, createValidation, updateValidation, legacyForceCreate, options) if err != nil { - log.With("object", objFromLegacy).Error("could not update in legacy storage", "err", err) + log.Error("failed to UPDATE in legacy storage", "err", err) return nil, false, err } @@ -350,7 +358,7 @@ func (d *dualWriter) Update(ctx context.Context, name string, objInfo rest.Updat if createdLegacy { legacyMeta, err := utils.MetaAccessor(objFromLegacy) if err != nil { - log.With("object", objFromLegacy).Error("could not get meta accessor for legacy object", "err", err) + log.Error("failed to get meta accessor for legacy object", "err", err) return nil, false, err } unifiedInfo = &wrappedUpdateInfo{ @@ -374,6 +382,7 @@ func (d *dualWriter) Update(ctx context.Context, name string, objInfo rest.Updat } // If we want to check unified errors just run it in foreground. if _, _, err := d.unified.Update(ctx, name, unifiedInfo, createValidation, updateValidation, unifiedForceCreate, options); err != nil { + log.Error("failed to UPDATE in unified storage", "err", err) return nil, false, err } return objFromLegacy, createdLegacy, nil @@ -391,7 +400,7 @@ func (d *dualWriter) DeleteCollection(ctx context.Context, deleteValidation rest deletedLegacy, err := d.legacy.DeleteCollection(ctx, deleteValidation, options, listOptions) if err != nil { - log.With("deleted", deletedLegacy).Error("failed to delete collection successfully from legacy storage", "err", err) + log.With("options", options).Error("failed to DELETE collection successfully from legacy storage", "err", err) return nil, err } @@ -403,14 +412,14 @@ func (d *dualWriter) DeleteCollection(ctx context.Context, deleteValidation rest go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() if _, err := d.unified.DeleteCollection(ctxBg, deleteValidation, options, listOptions); err != nil { - log.Error("failed background DELETE collection to unified storage", "err", err) + log.With("object", deletedLegacy).Error("failed background DELETE collection to unified storage", "err", err) } }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) return deletedLegacy, nil } // Otherwise we have to check the error and run it in the foreground. - if deletedStorage, err := d.unified.DeleteCollection(ctx, deleteValidation, options, listOptions); err != nil { - log.With("deleted", deletedStorage).Error("failed to delete collection successfully from Storage", "err", err) + if _, err := d.unified.DeleteCollection(ctx, deleteValidation, options, listOptions); err != nil { + log.With("object", deletedLegacy).Error("failed to DELETE collection successfully from Storage", "err", err) return nil, err } return deletedLegacy, nil From 8b31ec5040c4b15fca4f3ae9fe28780ccebb3e9a Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 30 Oct 2025 09:23:21 +0000 Subject: [PATCH 38/72] Chore: Convert to functional components (#112951) * refactor ErrorBoundary so it doesn't trigger the lint rule * refactor ErrorBoundaryAlert to functional component * convert StatPanel to a functional component * convert ServiceAccountPicker to a functional component * convert UserPicker to a functional component * don't need displayName when not memoized * convert TimelineChart to a functional component * convert UserLdapSyncInfo to a functional component * convert UserOrgs to functional component * convert OrgRow to a functional component * convert UserSessions to a functional component * convert TimePickerSettings to a functional component * convert DataSourcePluginSettings to a functional component * convert ExploreTimeControls to a functional component * convert SearchBarInput to a functional component * convert LiveConnectionWarning to a functional component * convert ConcatenateTransformerEditor * convert ConstantVariableEditor a functional component * convert VariableInput to a functional component * convert ConfigEditor to a functional component * convert CSVWavesEditor to a functional component --- eslint-suppressions.json | 86 +---- .../src/components/ErrorBoundary.tsx | 10 +- .../ErrorBoundary/ErrorBoundary.tsx | 23 +- .../Select/ServiceAccountPicker.tsx | 110 +++---- .../app/core/components/Select/UserPicker.tsx | 104 +++--- .../TimelineChart/TimelineChart.tsx | 121 +++---- .../app/features/admin/UserLdapSyncInfo.tsx | 138 ++++---- public/app/features/admin/UserOrgs.tsx | 300 ++++++++---------- public/app/features/admin/UserSessions.tsx | 195 ++++++------ .../DashboardSettings/TimePickerSettings.tsx | 113 ++++--- .../components/DataSourcePluginSettings.tsx | 51 ++- .../features/explore/ExploreTimeControls.tsx | 88 +++-- .../components/common/SearchBarInput.test.tsx | 5 +- .../components/common/SearchBarInput.tsx | 65 ++-- .../features/live/LiveConnectionWarning.tsx | 97 +++--- .../editors/ConcatenateTransformerEditor.tsx | 116 +++---- .../constant/ConstantVariableEditor.tsx | 15 +- .../pickers/shared/VariableInput.tsx | 57 ++-- .../components/ConfigEditor/ConfigEditor.tsx | 62 ++-- .../components/CSVWaveEditor.tsx | 63 ++-- public/app/plugins/panel/stat/StatPanel.tsx | 206 ++++++------ public/locales/en-US/grafana.json | 3 + 22 files changed, 924 insertions(+), 1104 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 2d84e04dadf..e068ca60eff 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -539,11 +539,6 @@ "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 @@ -666,11 +661,6 @@ "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/FormField/FormField.tsx": { "no-restricted-syntax": { "count": 1 @@ -1278,21 +1268,11 @@ "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 @@ -1342,11 +1322,6 @@ "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 @@ -1483,25 +1458,17 @@ "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": { + "public/app/features/admin/UserProfile.tsx": { "react-prefer-function-component/react-prefer-function-component": { "count": 1 } @@ -2399,9 +2366,6 @@ "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": { @@ -2752,11 +2716,6 @@ "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 @@ -2872,11 +2831,6 @@ "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 @@ -2977,11 +2931,6 @@ "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 @@ -3118,11 +3067,6 @@ "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 @@ -3578,11 +3522,6 @@ "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 @@ -3700,11 +3639,6 @@ "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 @@ -3787,9 +3721,6 @@ "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": { @@ -4004,11 +3935,6 @@ "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 @@ -4207,11 +4133,6 @@ "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 @@ -4920,11 +4841,6 @@ "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/packages/grafana-sql/src/components/ErrorBoundary.tsx b/packages/grafana-sql/src/components/ErrorBoundary.tsx index f7991415795..8500fb68fdf 100644 --- a/packages/grafana-sql/src/components/ErrorBoundary.tsx +++ b/packages/grafana-sql/src/components/ErrorBoundary.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { Component, ErrorInfo, PropsWithChildren } from 'react'; import { Trans } from '@grafana/i18n'; @@ -6,14 +6,14 @@ type Props = { fallBackComponent?: React.ReactNode; }; -export class ErrorBoundary extends React.Component, { hasError: boolean }> { - constructor(props: React.PropsWithChildren) { +export class ErrorBoundary extends Component, { hasError: boolean }> { + constructor(props: PropsWithChildren) { super(props); this.state = { hasError: false }; } - static getDerivedStateFromError() { - return { hasError: true }; + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + this.setState({ hasError: true }); } render() { diff --git a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx index dd49f34b985..818e919710c 100644 --- a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -1,6 +1,7 @@ -import { PureComponent, ReactNode, ComponentType, ErrorInfo } from 'react'; +import { PureComponent, ReactNode, ComponentType, ErrorInfo, memo } from 'react'; import { faro } from '@grafana/faro-web-sdk'; +import { t } from '@grafana/i18n'; import { Alert } from '../Alert/Alert'; @@ -116,15 +117,9 @@ export interface ErrorBoundaryAlertProps { errorLogger?: (error: Error) => void; } -export class ErrorBoundaryAlert extends PureComponent { - static defaultProps: Partial = { - title: 'An unexpected error happened', - style: 'alertbox', - }; - - render() { - const { title, children, style, dependencies, errorLogger, boundaryName } = this.props; - +export const ErrorBoundaryAlert = memo( + ({ title, children, style = 'alertbox', dependencies, errorLogger, boundaryName }: ErrorBoundaryAlertProps) => { + const alertTitle = title ?? t('grafana-ui.error-boundary.title', 'An unexpected error happened'); return ( {({ error, errorInfo }) => { @@ -134,7 +129,7 @@ export class ErrorBoundaryAlert extends PureComponent { if (style === 'alertbox') { return ( - +
{error && error.toString()}
@@ -144,12 +139,14 @@ export class ErrorBoundaryAlert extends PureComponent { ); } - return ; + return ; }} ); } -} +); + +ErrorBoundaryAlert.displayName = 'ErrorBoundaryAlert'; /** * HOC for wrapping a component in an error boundary. diff --git a/public/app/core/components/Select/ServiceAccountPicker.tsx b/public/app/core/components/Select/ServiceAccountPicker.tsx index 037404dcb2b..e9b200a4cb0 100644 --- a/public/app/core/components/Select/ServiceAccountPicker.tsx +++ b/public/app/core/components/Select/ServiceAccountPicker.tsx @@ -1,6 +1,6 @@ import debounce from 'debounce-promise'; import { isNil } from 'lodash'; -import { Component } from 'react'; +import { useMemo, useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; @@ -14,66 +14,58 @@ export interface Props { inputId?: string; } -export interface State { - isLoading: boolean; -} +export const ServiceAccountPicker = ({ className, onSelected, inputId }: Props) => { + const [isLoading, setIsLoading] = useState(false); -export class ServiceAccountPicker extends Component { - constructor(props: Props) { - super(props); - this.state = { isLoading: false }; - } + const search = useMemo( + () => + debounce( + async (query?: string) => { + setIsLoading(true); - search = debounce( - async (query?: string) => { - this.setState({ isLoading: true }); + if (isNil(query)) { + query = ''; + } - if (isNil(query)) { - query = ''; - } - - return getBackendSrv() - .get(`/api/serviceaccounts/search?query=${query}&perpage=100`) - .then((result: ServiceAccountsState) => { - return result.serviceAccounts.map((sa) => ({ - id: sa.id, - uid: sa.uid, - value: sa, - label: sa.login, - imgUrl: sa.avatarUrl, - login: sa.login, - })); - }) - .finally(() => { - this.setState({ isLoading: false }); - }); - }, - 300, - { leading: true } + return getBackendSrv() + .get(`/api/serviceaccounts/search?query=${query}&perpage=100`) + .then((result: ServiceAccountsState) => { + return result.serviceAccounts.map((sa) => ({ + id: sa.id, + uid: sa.uid, + value: sa, + label: sa.login, + imgUrl: sa.avatarUrl, + login: sa.login, + })); + }) + .finally(() => { + setIsLoading(false); + }); + }, + 300, + { leading: true } + ), + [] ); - render() { - const { className, onSelected, inputId } = this.props; - const { isLoading } = this.state; - - return ( -
- -
- ); - } -} + return ( +
+ +
+ ); +}; diff --git a/public/app/core/components/Select/UserPicker.tsx b/public/app/core/components/Select/UserPicker.tsx index 5284cbeca5f..e7d4110d09b 100644 --- a/public/app/core/components/Select/UserPicker.tsx +++ b/public/app/core/components/Select/UserPicker.tsx @@ -1,6 +1,6 @@ import debounce from 'debounce-promise'; import { isNil } from 'lodash'; -import { Component } from 'react'; +import { useMemo, useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; @@ -14,63 +14,55 @@ export interface Props { inputId?: string; } -export interface State { - isLoading: boolean; -} +export const UserPicker = ({ className, onSelected, inputId }: Props) => { + const [isLoading, setIsLoading] = useState(false); -export class UserPicker extends Component { - constructor(props: Props) { - super(props); - this.state = { isLoading: false }; - } + const search = useMemo( + () => + debounce( + async (query?: string) => { + setIsLoading(true); - search = debounce( - async (query?: string) => { - this.setState({ isLoading: true }); + if (isNil(query)) { + query = ''; + } - if (isNil(query)) { - query = ''; - } - - return getBackendSrv() - .get(`/api/org/users/lookup?query=${query}&limit=100`) - .then((result: OrgUser[]) => { - return result.map((user) => ({ - id: user.userId, - uid: user.uid, - value: user, - label: user.login, - imgUrl: user.avatarUrl, - login: user.login, - })); - }) - .finally(() => { - this.setState({ isLoading: false }); - }); - }, - 300, - { leading: true } + return getBackendSrv() + .get(`/api/org/users/lookup?query=${query}&limit=100`) + .then((result: OrgUser[]) => { + return result.map((user) => ({ + id: user.userId, + uid: user.uid, + value: user, + label: user.login, + imgUrl: user.avatarUrl, + login: user.login, + })); + }) + .finally(() => { + setIsLoading(false); + }); + }, + 300, + { leading: true } + ), + [] ); - render() { - const { className, onSelected, inputId } = this.props; - const { isLoading } = this.state; - - return ( -
- -
- ); - } -} + return ( +
+ +
+ ); +}; diff --git a/public/app/core/components/TimelineChart/TimelineChart.tsx b/public/app/core/components/TimelineChart/TimelineChart.tsx index f4317a127b7..ce6e4a1f4c1 100644 --- a/public/app/core/components/TimelineChart/TimelineChart.tsx +++ b/public/app/core/components/TimelineChart/TimelineChart.tsx @@ -1,4 +1,4 @@ -import { Component } from 'react'; +import { useCallback } from 'react'; import { DataFrame, FALLBACK_COLOR, FieldType, TimeRange } from '@grafana/data'; import { VisibilityMode, TimelineValueAlignment, TooltipDisplayMode, VizTooltipOptions } from '@grafana/schema'; @@ -25,69 +25,76 @@ export interface TimelineProps extends Omit { - getValueColor = (frameIdx: number, fieldIdx: number, value: unknown) => { - const field = this.props.frames[frameIdx]?.fields[fieldIdx]; +export const TimelineChart = (props: TimelineProps) => { + const { frames, timeZone, rowHeight, tooltip, legend, legendItems } = props; - if (field?.display) { - const disp = field.display(value); // will apply color modes - if (disp.color) { - return disp.color; + const getValueColor = useCallback( + (frameIdx: number, fieldIdx: number, value: unknown) => { + const field = frames[frameIdx]?.fields[fieldIdx]; + + if (field?.display) { + const disp = field.display(value); // will apply color modes + if (disp.color) { + return disp.color; + } } - } - return FALLBACK_COLOR; - }; + return FALLBACK_COLOR; + }, + [frames] + ); - prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => { - return preparePlotConfigBuilder({ - frame: alignedFrame, - getTimeRange, - allFrames: this.props.frames, - ...this.props, + const prepConfig = useCallback( + (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => { + return preparePlotConfigBuilder({ + frame: alignedFrame, + getTimeRange, + allFrames: frames, + ...props, - // Ensure timezones is passed as an array - timeZones: Array.isArray(this.props.timeZone) ? this.props.timeZone : [this.props.timeZone], + // Ensure timezones is passed as an array + timeZones: Array.isArray(timeZone) ? timeZone : [timeZone], - // When there is only one row, use the full space - rowHeight: alignedFrame.fields.length > 2 ? this.props.rowHeight : 1, - getValueColor: this.getValueColor, + // When there is only one row, use the full space + rowHeight: alignedFrame.fields.length > 2 ? rowHeight : 1, + getValueColor: getValueColor, - hoverMulti: this.props.tooltip?.mode === TooltipDisplayMode.Multi, - }); - }; + hoverMulti: tooltip?.mode === TooltipDisplayMode.Multi, + }); + }, + [frames, props, timeZone, rowHeight, getValueColor, tooltip] + ); - renderLegend = (config: UPlotConfigBuilder) => { - const { legend, legendItems } = this.props; + const renderLegend = useCallback( + (config: UPlotConfigBuilder) => { + if (!config || !legendItems || !legend || legend.showLegend === false) { + return null; + } - if (!config || !legendItems || !legend || legend.showLegend === false) { - return null; - } + return ( + + + + ); + }, + [legend, legendItems] + ); - return ( - - - - ); - }; - - render() { - return ( - f.type === FieldType.time, - y: (f) => - f.type === FieldType.number || - f.type === FieldType.boolean || - f.type === FieldType.string || - f.type === FieldType.enum, - }} - prepConfig={this.prepConfig} - propsToDiff={propsToDiff} - renderLegend={this.renderLegend} - omitHideFromViz={true} - /> - ); - } -} + return ( + f.type === FieldType.time, + y: (f) => + f.type === FieldType.number || + f.type === FieldType.boolean || + f.type === FieldType.string || + f.type === FieldType.enum, + }} + prepConfig={prepConfig} + propsToDiff={propsToDiff} + renderLegend={renderLegend} + omitHideFromViz={true} + /> + ); +}; diff --git a/public/app/features/admin/UserLdapSyncInfo.tsx b/public/app/features/admin/UserLdapSyncInfo.tsx index 1baf9e219a2..4277fa4c814 100644 --- a/public/app/features/admin/UserLdapSyncInfo.tsx +++ b/public/app/features/admin/UserLdapSyncInfo.tsx @@ -1,4 +1,4 @@ -import { PureComponent } from 'react'; +import { memo } from 'react'; import { dateTimeFormat } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; @@ -16,80 +16,72 @@ interface Props { onUserSync: () => void; } -interface State {} - const format = 'dddd YYYY-MM-DD HH:mm zz'; const debugLDAPMappingBaseURL = '/admin/authentication/ldap'; -export class UserLdapSyncInfo extends PureComponent { - onUserSync = () => { - this.props.onUserSync(); - }; +export const UserLdapSyncInfo = memo(({ ldapSyncInfo, user, onUserSync }: Props) => { + const nextSyncSuccessful = ldapSyncInfo && ldapSyncInfo.nextSync; + const nextSyncTime = nextSyncSuccessful ? dateTimeFormat(ldapSyncInfo.nextSync, { format }) : ''; + const debugLDAPMappingURL = `${debugLDAPMappingBaseURL}?username=${user && user.login}`; + const canReadLDAPUser = contextSrv.hasPermission(AccessControlAction.LDAPUsersRead); + const canSyncLDAPUser = contextSrv.hasPermission(AccessControlAction.LDAPUsersSync); - render() { - const { ldapSyncInfo, user } = this.props; - const nextSyncSuccessful = ldapSyncInfo && ldapSyncInfo.nextSync; - const nextSyncTime = nextSyncSuccessful ? dateTimeFormat(ldapSyncInfo.nextSync, { format }) : ''; - const debugLDAPMappingURL = `${debugLDAPMappingBaseURL}?username=${user && user.login}`; - const canReadLDAPUser = contextSrv.hasPermission(AccessControlAction.LDAPUsersRead); - const canSyncLDAPUser = contextSrv.hasPermission(AccessControlAction.LDAPUsersSync); - - return ( - <> -

- LDAP Synchronisation -

-
-
-
- - - - - - - - - - - -
- External sync - - - User synced via LDAP. Some changes must be done in LDAP or mappings. - - - -
- Next scheduled synchronization - - {ldapSyncInfo.enabled ? ( - nextSyncTime - ) : ( - Not enabled - )} -
- -
- {canSyncLDAPUser && ( - - )} - {canReadLDAPUser && ( - - Debug LDAP Mapping - - )} -
+ return ( + <> +

+ LDAP Synchronisation +

+
+
+ + + + + + + + + + + + +
+ External sync + + + User synced via LDAP. Some changes must be done in LDAP or mappings. + + + +
+ Next scheduled synchronization + + {ldapSyncInfo.enabled ? ( + nextSyncTime + ) : ( + Not enabled + )} +
- - ); - } -} +
+ {canSyncLDAPUser && ( + + )} + {canReadLDAPUser && ( + + Debug LDAP Mapping + + )} +
+
+ + ); +}); +UserLdapSyncInfo.displayName = 'UserLdapSyncInfo'; diff --git a/public/app/features/admin/UserOrgs.tsx b/public/app/features/admin/UserOrgs.tsx index b7083bf7051..a0169a1022a 100644 --- a/public/app/features/admin/UserOrgs.tsx +++ b/public/app/features/admin/UserOrgs.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import { createRef, PureComponent, ReactElement } from 'react'; +import { memo, PureComponent, ReactElement, useEffect, useRef, useState } from 'react'; import { GrafanaTheme2, OrgRole } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; @@ -10,10 +10,8 @@ import { Icon, Modal, stylesFactory, - Themeable2, Tooltip, useStyles2, - withTheme2, Stack, TextLink, } from '@grafana/ui'; @@ -37,73 +35,63 @@ interface Props { onOrgAdd: (orgId: number, role: OrgRole) => void; } -interface State { - showAddOrgModal: boolean; -} +export const UserOrgs = memo(({ user, orgs, isExternalUser, onOrgRoleChange, onOrgRemove, onOrgAdd }: Props) => { + const [showAddOrgModal, setShowAddOrgModal] = useState(false); + const addToOrgButtonRef = useRef(null); -export class UserOrgs extends PureComponent { - addToOrgButtonRef = createRef(); - state = { - showAddOrgModal: false, + const showOrgAddModal = () => { + setShowAddOrgModal(true); }; - showOrgAddModal = () => { - this.setState({ showAddOrgModal: true }); + const dismissOrgAddModal = () => { + setShowAddOrgModal(false); + addToOrgButtonRef.current?.focus(); }; - dismissOrgAddModal = () => { - this.setState({ showAddOrgModal: false }, () => { - this.addToOrgButtonRef.current?.focus(); - }); - }; + const canAddToOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersAdd) && !isExternalUser; - render() { - const { user, orgs, isExternalUser, onOrgRoleChange, onOrgRemove, onOrgAdd } = this.props; - const { showAddOrgModal } = this.state; + return ( +
+

+ Organizations +

+ + + + {orgs.map((org, index) => ( + + ))} + +
- const canAddToOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersAdd) && !isExternalUser; - return ( -
-

- Organizations -

- - - - {orgs.map((org, index) => ( - - ))} - -
+
+ {canAddToOrg && ( + + )} +
+ +
+
+ ); +}); +UserOrgs.displayName = 'UserOrgs'; -
- {canAddToOrg && ( - - )} -
- -
-
- ); - } -} - -const getOrgRowStyles = stylesFactory((theme: GrafanaTheme2) => { +const getOrgRowStyles = (theme: GrafanaTheme2) => { return { removeButton: css({ marginRight: '0.6rem', @@ -130,9 +118,9 @@ const getOrgRowStyles = stylesFactory((theme: GrafanaTheme2) => { marginRight: theme.spacing(1), }), }; -}); +}; -interface OrgRowProps extends Themeable2 { +interface OrgRowProps { user?: UserDTO; org: UserOrg; isExternalUser?: boolean; @@ -140,124 +128,116 @@ interface OrgRowProps extends Themeable2 { onOrgRoleChange: (orgId: number, newRole: OrgRole) => void; } -class UnThemedOrgRow extends PureComponent { - state = { - currentRole: this.props.org.role, - isChangingRole: false, - roleOptions: [], - }; +const OrgRow = memo(({ user, org, isExternalUser, onOrgRemove, onOrgRoleChange }: OrgRowProps) => { + const [currentRole, setCurrentRole] = useState(org.role); + const [isChangingRole, setIsChangingRole] = useState(false); + const [roleOptions, setRoleOptions] = useState([]); + const styles = useStyles2(getOrgRowStyles); - componentDidMount() { + useEffect(() => { if (contextSrv.licensedAccessControlEnabled()) { if (contextSrv.hasPermission(AccessControlAction.ActionRolesList)) { - fetchRoleOptions(this.props.org.orgId) - .then((roles) => this.setState({ roleOptions: roles })) + fetchRoleOptions(org.orgId) + .then((roles) => setRoleOptions(roles)) .catch((e) => console.error(e)); } } - } + }, [org.orgId]); - onOrgRemove = async () => { - const { org } = this.props; - this.props.onOrgRemove(org.orgId); + const handleOrgRemove = async () => { + onOrgRemove(org.orgId); }; - onChangeRoleClick = () => { - const { org } = this.props; - this.setState({ isChangingRole: true, currentRole: org.role }); + const handleChangeRoleClick = () => { + setIsChangingRole(true); + setCurrentRole(org.role); }; - onOrgRoleChange = (newRole: OrgRole) => { - this.setState({ currentRole: newRole }); + const handleOrgRoleChange = (newRole: OrgRole) => { + setCurrentRole(newRole); }; - onOrgRoleSave = () => { - this.props.onOrgRoleChange(this.props.org.orgId, this.state.currentRole); + const handleOrgRoleSave = () => { + onOrgRoleChange(org.orgId, currentRole); }; - onCancelClick = () => { - this.setState({ isChangingRole: false }); + const handleCancelClick = () => { + setIsChangingRole(false); }; - onBasicRoleChange = (newRole: OrgRole) => { - this.props.onOrgRoleChange(this.props.org.orgId, newRole); + const handleBasicRoleChange = (newRole: OrgRole) => { + onOrgRoleChange(org.orgId, newRole); }; - render() { - const { user, org, isExternalUser, theme } = this.props; - const authSource = user?.authLabels?.length && user?.authLabels[0]; - const lockMessage = authSource ? `Synced via ${authSource}` : ''; - const { currentRole, isChangingRole } = this.state; - const styles = getOrgRowStyles(theme); - const labelClass = cx('width-16', styles.label); - const canChangeRole = contextSrv.hasPermission(AccessControlAction.OrgUsersWrite); - const canRemoveFromOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersRemove) && !isExternalUser; - const rolePickerDisabled = isExternalUser || !canChangeRole; + const authSource = user?.authLabels?.length && user?.authLabels[0]; + const lockMessage = authSource ? `Synced via ${authSource}` : ''; + const labelClass = cx('width-16', styles.label); + const canChangeRole = contextSrv.hasPermission(AccessControlAction.OrgUsersWrite); + const canRemoveFromOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersRemove) && !isExternalUser; + const rolePickerDisabled = isExternalUser || !canChangeRole; - const inputId = `${org.name}-input`; - return ( - - - - - {contextSrv.licensedAccessControlEnabled() ? ( - -
-
- -
- {isExternalUser && } + const inputId = `${org.name}-input`; + return ( + + + + + {contextSrv.licensedAccessControlEnabled() ? ( + +
+
+
- - ) : ( - <> - {isChangingRole ? ( - - - - ) : ( - {org.role} - )} - - {canChangeRole && ( - - )} - - - )} - - {canRemoveFromOrg && ( - - {t('admin.user-orgs.remove-button', 'Remove from organization')} - - )} + {isExternalUser && } +
- - ); - } -} - -const OrgRow = withTheme2(UnThemedOrgRow); + ) : ( + <> + {isChangingRole ? ( + + + + ) : ( + {org.role} + )} + + {canChangeRole && ( + + )} + + + )} + + {canRemoveFromOrg && ( + + {t('admin.user-orgs.remove-button', 'Remove from organization')} + + )} + + + ); +}); +OrgRow.displayName = 'OrgRow'; const getAddToOrgModalStyles = stylesFactory(() => ({ modal: css({ diff --git a/public/app/features/admin/UserSessions.tsx b/public/app/features/admin/UserSessions.tsx index f05278d7884..16fadaa8313 100644 --- a/public/app/features/admin/UserSessions.tsx +++ b/public/app/features/admin/UserSessions.tsx @@ -1,4 +1,4 @@ -import { createRef, PureComponent } from 'react'; +import { memo, useRef, useState } from 'react'; import { Trans, t } from '@grafana/i18n'; import { ConfirmButton, ConfirmModal, Button, Stack } from '@grafana/ui'; @@ -15,120 +15,107 @@ interface Props { onAllSessionsRevoke: () => void; } -interface State { - showLogoutModal: boolean; -} +export const UserSessions = memo(({ sessions, onSessionRevoke, onAllSessionsRevoke }: Props) => { + const [showLogoutModal, setShowLogoutModal] = useState(false); + const forceAllLogoutButton = useRef(null); -class BaseUserSessions extends PureComponent { - forceAllLogoutButton = createRef(); - state: State = { - showLogoutModal: false, + const showLogoutConfirmationModal = () => { + setShowLogoutModal(true); }; - showLogoutConfirmationModal = () => { - this.setState({ showLogoutModal: true }); + const dismissLogoutConfirmationModal = () => { + setShowLogoutModal(false); + forceAllLogoutButton.current?.focus(); }; - dismissLogoutConfirmationModal = () => { - this.setState({ showLogoutModal: false }, () => { - this.forceAllLogoutButton.current?.focus(); - }); - }; - - onSessionRevoke = (id: number) => { + const handleSessionRevoke = (id: number) => { return () => { - this.props.onSessionRevoke(id); + onSessionRevoke(id); }; }; - onAllSessionsRevoke = () => { - this.setState({ showLogoutModal: false }); - this.props.onAllSessionsRevoke(); + const handleAllSessionsRevoke = () => { + setShowLogoutModal(false); + onAllSessionsRevoke(); }; - render() { - const { sessions } = this.props; - const { showLogoutModal } = this.state; + const canLogout = contextSrv.hasPermission(AccessControlAction.UsersLogout); - const canLogout = contextSrv.hasPermission(AccessControlAction.UsersLogout); + return ( +
+

+ Sessions +

+ +
+ + + + + + + + + + + + {sessions && + sessions.map((session, index) => ( + + + + + + + + + ))} + +
+ Last seen + + Logged on + + IP address + + Browser and OS + + Identity Provider +
{session.isActive ? t('admin.user-sessions.now', 'Now') : session.seenAt}{formatDate(session.createdAt, { dateStyle: 'long' })}{session.clientIp}{`${session.browser} on ${session.os} ${session.osVersion}`} + {session.authModule && } + + {canLogout && ( + + {t('admin.user-sessions.force-logout-button', 'Force logout')} + + )} +
+
- return ( -
-

- Sessions -

- -
- - - - - - - - - - - - {sessions && - sessions.map((session, index) => ( - - - - - - - - - ))} - -
- Last seen - - Logged on - - IP address - - Browser and OS - - Identity Provider -
{session.isActive ? t('admin.user-sessions.now', 'Now') : session.seenAt}{formatDate(session.createdAt, { dateStyle: 'long' })}{session.clientIp}{`${session.browser} on ${session.os} ${session.osVersion}`} - {session.authModule && } - - {canLogout && ( - - {t('admin.user-sessions.force-logout-button', 'Force logout')} - - )} -
-
- -
- {canLogout && sessions.length > 0 && ( - +
+ {canLogout && sessions.length > 0 && ( + + )} + -
- -
- ); - } -} - -export const UserSessions = BaseUserSessions; + confirmText={t('admin.base-user-sessions.confirmText-force-logout', 'Force logout')} + onConfirm={handleAllSessionsRevoke} + onDismiss={dismissLogoutConfirmationModal} + /> +
+
+
+ ); +}); +UserSessions.displayName = 'UserSessions'; diff --git a/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx index bfcd9a5e66d..ba88db1e1bf 100644 --- a/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx @@ -1,6 +1,5 @@ import { isEmpty } from 'lodash'; -import { PureComponent } from 'react'; -import * as React from 'react'; +import { FormEvent, memo, useState } from 'react'; import { rangeUtil, TimeZone } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; @@ -24,49 +23,58 @@ interface Props { liveNow?: boolean; } -interface State { - isNowDelayValid: boolean; -} +export const TimePickerSettings = memo( + ({ + onWeekStartChange, + onTimeZoneChange, + onRefreshIntervalChange, + onNowDelayChange, + onHideTimePickerChange, + onLiveNowChange, + refreshIntervals, + timePickerHidden, + nowDelay, + timezone, + weekStart, + liveNow, + }: Props) => { + const [isNowDelayValid, setIsNowDelayValid] = useState(true); -export class TimePickerSettings extends PureComponent { - state: State = { isNowDelayValid: true }; + const handleNowDelayChange = (event: FormEvent) => { + const value = event.currentTarget.value; - onNowDelayChange = (event: React.FormEvent) => { - const value = event.currentTarget.value; + if (isEmpty(value)) { + setIsNowDelayValid(true); + return onNowDelayChange(value); + } - if (isEmpty(value)) { - this.setState({ isNowDelayValid: true }); - return this.props.onNowDelayChange(value); - } + if (rangeUtil.isValidTimeSpan(value)) { + setIsNowDelayValid(true); + return onNowDelayChange(value); + } - if (rangeUtil.isValidTimeSpan(value)) { - this.setState({ isNowDelayValid: true }); - return this.props.onNowDelayChange(value); - } + setIsNowDelayValid(false); + }; - this.setState({ isNowDelayValid: false }); - }; + const handleHideTimePickerChange = () => { + onHideTimePickerChange(!timePickerHidden); + }; - onHideTimePickerChange = () => { - this.props.onHideTimePickerChange(!this.props.timePickerHidden); - }; + const handleLiveNowChange = () => { + onLiveNowChange(!liveNow); + }; - onLiveNowChange = () => { - this.props.onLiveNowChange(!this.props.liveNow); - }; + const handleTimeZoneChange = (timeZone?: string) => { + if (typeof timeZone !== 'string') { + return; + } + onTimeZoneChange(timeZone); + }; - onTimeZoneChange = (timeZone?: string) => { - if (typeof timeZone !== 'string') { - return; - } - this.props.onTimeZoneChange(timeZone); - }; + const handleWeekStartChange = (weekStart?: WeekStart) => { + onWeekStartChange(weekStart); + }; - onWeekStartChange = (weekStart?: WeekStart) => { - this.props.onWeekStartChange(weekStart); - }; - - render() { return ( { @@ -85,17 +93,9 @@ export class TimePickerSettings extends PureComponent { label={t('dashboard-settings.time-picker.week-start-label', 'Week start')} data-testid={selectors.components.WeekStartPicker.containerV2} > - + - + { > - + { 'Continuously update panels when the time range includes the current time' )} > - + ); } -} +); +TimePickerSettings.displayName = 'TimePickerSettings'; diff --git a/public/app/features/datasources/components/DataSourcePluginSettings.tsx b/public/app/features/datasources/components/DataSourcePluginSettings.tsx index f920837275b..556896147fe 100644 --- a/public/app/features/datasources/components/DataSourcePluginSettings.tsx +++ b/public/app/features/datasources/components/DataSourcePluginSettings.tsx @@ -1,4 +1,4 @@ -import { createElement, PureComponent } from 'react'; +import { createElement, memo } from 'react'; import { DataSourcePluginMeta, DataSourceSettings } from '@grafana/data'; import { writableProxy } from 'app/features/plugins/extensions/utils'; @@ -12,36 +12,23 @@ export interface Props { onModelChange: (dataSource: DataSourceSettings) => void; } -export class DataSourcePluginSettings extends PureComponent { - constructor(props: Props) { - super(props); - - this.onModelChanged = this.onModelChanged.bind(this); +export const DataSourcePluginSettings = memo(({ plugin, dataSource, onModelChange }: Props) => { + if (!plugin) { + return null; } - onModelChanged = (dataSource: DataSourceSettings) => { - this.props.onModelChange(dataSource); - }; - - render() { - const { plugin, dataSource } = this.props; - - if (!plugin) { - return null; - } - - return ( -
- {plugin.components.ConfigEditor && - createElement(plugin.components.ConfigEditor, { - options: writableProxy(dataSource, { - source: 'datasource', - pluginId: plugin.meta?.id, - pluginVersion: plugin.meta?.info?.version, - }), - onOptionsChange: this.onModelChanged, - })} -
- ); - } -} + return ( +
+ {plugin.components.ConfigEditor && + createElement(plugin.components.ConfigEditor, { + options: writableProxy(dataSource, { + source: 'datasource', + pluginId: plugin.meta?.id, + pluginVersion: plugin.meta?.info?.version, + }), + onOptionsChange: onModelChange, + })} +
+ ); +}); +DataSourcePluginSettings.displayName = 'DataSourcePluginSettings'; diff --git a/public/app/features/explore/ExploreTimeControls.tsx b/public/app/features/explore/ExploreTimeControls.tsx index 07c92e8fba5..ef616131b6e 100644 --- a/public/app/features/explore/ExploreTimeControls.tsx +++ b/public/app/features/explore/ExploreTimeControls.tsx @@ -1,5 +1,3 @@ -import { Component } from 'react'; - import { TimeRange, RawTimeRange, dateTimeForTimeZone, dateMath } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; import { TimeZone } from '@grafana/schema'; @@ -22,9 +20,19 @@ export interface Props { onChangeFiscalYearStartMonth: (fiscalYearStartMonth: number) => void; } -export class ExploreTimeControls extends Component { - onMoveTimePicker = (direction: number) => { - const { range, onChangeTime, timeZone } = this.props; +export const ExploreTimeControls = ({ + range, + timeZone, + fiscalYearStartMonth, + splitted, + syncedTimes, + onChangeTimeSync, + hideText, + onChangeTimeZone, + onChangeFiscalYearStartMonth, + onChangeTime, +}: Props) => { + const onMoveTimePicker = (direction: number) => { const { from, to } = getShiftedTimeRange(direction, range); const nextTimeRange = { from: dateTimeForTimeZone(timeZone, from), @@ -34,14 +42,14 @@ export class ExploreTimeControls extends Component { onChangeTime(nextTimeRange); }; - onMoveForward = () => this.onMoveTimePicker(1); - onMoveBack = () => this.onMoveTimePicker(-1); + const onMoveForward = () => onMoveTimePicker(1); + const onMoveBack = () => onMoveTimePicker(-1); - onChangeTimePicker = (timeRange: TimeRange) => { + const onChangeTimePicker = (timeRange: TimeRange) => { const adjustedFrom = dateMath.isMathString(timeRange.raw.from) ? timeRange.raw.from : timeRange.from; const adjustedTo = dateMath.isMathString(timeRange.raw.to) ? timeRange.raw.to : timeRange.to; - this.props.onChangeTime({ + onChangeTime({ from: adjustedFrom, to: adjustedTo, }); @@ -52,8 +60,7 @@ export class ExploreTimeControls extends Component { }); }; - onZoom = () => { - const { range, onChangeTime, timeZone } = this.props; + const onZoom = () => { const { from, to } = getZoomedTimeRange(range, 2); const nextTimeRange = { from: dateTimeForTimeZone(timeZone, from), @@ -63,40 +70,27 @@ export class ExploreTimeControls extends Component { onChangeTime(nextTimeRange); }; - render() { - const { - range, - timeZone, - fiscalYearStartMonth, - splitted, - syncedTimes, - onChangeTimeSync, - hideText, - onChangeTimeZone, - onChangeFiscalYearStartMonth, - } = this.props; - const timeSyncButton = splitted ? : undefined; - const timePickerCommonProps = { - value: range, - timeZone, - fiscalYearStartMonth, - onMoveBackward: this.onMoveBack, - onMoveForward: this.onMoveForward, - onZoom: this.onZoom, - hideText, - }; + const timeSyncButton = splitted ? : undefined; + const timePickerCommonProps = { + value: range, + timeZone, + fiscalYearStartMonth, + onMoveBackward: onMoveBack, + onMoveForward: onMoveForward, + onZoom: onZoom, + hideText, + }; - return ( - - ); - } -} + return ( + + ); +}; diff --git a/public/app/features/explore/TraceView/components/common/SearchBarInput.test.tsx b/public/app/features/explore/TraceView/components/common/SearchBarInput.test.tsx index e613c07fcfc..6e7b590a016 100644 --- a/public/app/features/explore/TraceView/components/common/SearchBarInput.test.tsx +++ b/public/app/features/explore/TraceView/components/common/SearchBarInput.test.tsx @@ -18,15 +18,16 @@ import SearchBarInput from './SearchBarInput'; describe('SearchBarInput', () => { describe('rendering', () => { + const onChange = jest.fn(); it('renders as expected with no value', () => { - render(); + render(); const searchBarInput = screen.queryByPlaceholderText('Find...'); expect(searchBarInput).toBeInTheDocument(); expect(searchBarInput?.getAttribute('value')).toEqual(''); }); it('renders as expected with value', () => { - render(); + render(); const searchBarInput = screen.queryByPlaceholderText('Find...'); expect(searchBarInput).toBeInTheDocument(); expect(searchBarInput?.getAttribute('value')).toEqual('value'); diff --git a/public/app/features/explore/TraceView/components/common/SearchBarInput.tsx b/public/app/features/explore/TraceView/components/common/SearchBarInput.tsx index 58db71fd862..a6527ca1c0e 100644 --- a/public/app/features/explore/TraceView/components/common/SearchBarInput.tsx +++ b/public/app/features/explore/TraceView/components/common/SearchBarInput.tsx @@ -12,49 +12,44 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from 'react'; +import { memo } from 'react'; import { t } from '@grafana/i18n'; import { IconButton, Input } from '@grafana/ui'; type Props = { - value: string | undefined; + value?: string; onChange: (value: string) => void; }; -export default class SearchBarInput extends React.PureComponent { - static defaultProps: Partial = { - value: undefined, +const SearchBarInput = memo(({ value, onChange }: Props) => { + const clearUiFind = () => { + onChange(''); }; - clearUiFind = () => { - this.props.onChange(''); - }; - - render() { - const { value } = this.props; - - const suffix = ( - <> - {value && value.length && ( - - )} - - ); - - return ( -
- this.props.onChange(e.currentTarget.value)} - suffix={suffix} - value={value} + const suffix = ( + <> + {value && value.length && ( + -
- ); - } -} + )} + + ); + + return ( +
+ onChange(e.currentTarget.value)} + suffix={suffix} + value={value} + /> +
+ ); +}); +SearchBarInput.displayName = 'SearchBarInput'; + +export default SearchBarInput; diff --git a/public/app/features/live/LiveConnectionWarning.tsx b/public/app/features/live/LiveConnectionWarning.tsx index 27595f0b908..2f78ebd22db 100644 --- a/public/app/features/live/LiveConnectionWarning.tsx +++ b/public/app/features/live/LiveConnectionWarning.tsx @@ -1,72 +1,63 @@ import { css } from '@emotion/css'; -import { PureComponent } from 'react'; +import { memo, useEffect, useRef, useState } from 'react'; import { Unsubscribable } from 'rxjs'; import { GrafanaTheme2, OrgRole } from '@grafana/data'; import { t } from '@grafana/i18n'; import { config, getGrafanaLiveSrv } from '@grafana/runtime'; -import { Alert, stylesFactory } from '@grafana/ui'; +import { Alert, useStyles2 } from '@grafana/ui'; import { contextSrv } from 'app/core/services/context_srv'; export interface Props {} -export interface State { - show?: boolean; -} +export const LiveConnectionWarning = memo(function LiveConnectionWarning() { + const [show, setShow] = useState(undefined); + const subscriptionRef = useRef(); + const styles = useStyles2(getStyle); -export class LiveConnectionWarning extends PureComponent { - subscription?: Unsubscribable; - styles = getStyle(config.theme2); - state: State = {}; - - componentDidMount() { + useEffect(() => { // Only show the error in development mode if (process.env.NODE_ENV === 'development') { // Wait a second to listen for server errors - setTimeout(this.initListener, 1500); + const timer = setTimeout(() => { + const live = getGrafanaLiveSrv(); + if (live) { + subscriptionRef.current = live.getConnectionState().subscribe({ + next: (v) => { + setShow(!v); + }, + }); + } + }, 1500); + + return () => { + clearTimeout(timer); + if (subscriptionRef.current) { + subscriptionRef.current.unsubscribe(); + } + }; } + + return undefined; + }, []); + + if (show) { + if (!contextSrv.isSignedIn || !config.liveEnabled || contextSrv.user.orgRole === OrgRole.None) { + return null; // do not show the warning for anonymous users or ones with no org (and /login page etc) + } + + return ( + + ); } + return null; +}); - initListener = () => { - const live = getGrafanaLiveSrv(); - if (live) { - this.subscription = live.getConnectionState().subscribe({ - next: (v) => { - this.setState({ show: !v }); - }, - }); - } - }; - - componentWillUnmount() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - } - - render() { - const { show } = this.state; - if (show) { - if (!contextSrv.isSignedIn || !config.liveEnabled || contextSrv.user.orgRole === OrgRole.None) { - return null; // do not show the warning for anonymous users or ones with no org (and /login page etc) - } - - return ( - - ); - } - return null; - } -} - -const getStyle = stylesFactory((theme: GrafanaTheme2) => ({ +const getStyle = (theme: GrafanaTheme2) => ({ warn: css({ position: 'fixed', bottom: 0, @@ -76,4 +67,4 @@ const getStyle = stylesFactory((theme: GrafanaTheme2) => ({ zIndex: theme.zIndex.portal, cursor: 'wait', }), -})); +}); diff --git a/public/app/features/transformers/editors/ConcatenateTransformerEditor.tsx b/public/app/features/transformers/editors/ConcatenateTransformerEditor.tsx index e31ea83a565..91bcb2c1542 100644 --- a/public/app/features/transformers/editors/ConcatenateTransformerEditor.tsx +++ b/public/app/features/transformers/editors/ConcatenateTransformerEditor.tsx @@ -1,4 +1,4 @@ -import { PureComponent, ChangeEvent } from 'react'; +import { memo, ChangeEvent } from 'react'; import { DataTransformerID, @@ -18,13 +18,8 @@ import lightImage from '../images/light/concatenate.svg'; interface ConcatenateTransformerEditorProps extends TransformerUIProps {} -export class ConcatenateTransformerEditor extends PureComponent { - constructor(props: ConcatenateTransformerEditorProps) { - super(props); - } - - onModeChanged = (value: SelectableValue) => { - const { options, onChange } = this.props; +export const ConcatenateTransformerEditor = memo(({ options, onChange }: ConcatenateTransformerEditorProps) => { + const onModeChanged = (value: SelectableValue) => { const frameNameMode = value.value ?? ConcatenateFrameNameMode.FieldName; onChange({ ...options, @@ -32,74 +27,63 @@ export class ConcatenateTransformerEditor extends PureComponent) => { - const { options } = this.props; - this.props.onChange({ + const onLabelChanged = (evt: ChangeEvent) => { + onChange({ ...options, frameNameLabel: evt.target.value, }); }; - //--------------------------------------------------------- - // Render - //--------------------------------------------------------- + const nameModes: Array> = [ + { + value: ConcatenateFrameNameMode.FieldName, + label: t( + 'transformers.concatenate-transformer-editor.name-modes.label.copy-frame-name-to-field', + 'Copy frame name to field name' + ), + }, + { + value: ConcatenateFrameNameMode.Label, + label: t( + 'transformers.concatenate-transformer-editor.name-modes.label.label-frame', + 'Add a label with the frame name' + ), + }, + { + value: ConcatenateFrameNameMode.Drop, + label: t( + 'transformers.concatenate-transformer-editor.name-modes.label.ignore-the-frame-name', + 'Ignore the frame name' + ), + }, + ]; - render() { - const { options } = this.props; - const nameModes: Array> = [ - { - value: ConcatenateFrameNameMode.FieldName, - label: t( - 'transformers.concatenate-transformer-editor.name-modes.label.copy-frame-name-to-field', - 'Copy frame name to field name' - ), - }, - { - value: ConcatenateFrameNameMode.Label, - label: t( - 'transformers.concatenate-transformer-editor.name-modes.label.label-frame', - 'Add a label with the frame name' - ), - }, - { - value: ConcatenateFrameNameMode.Drop, - label: t( - 'transformers.concatenate-transformer-editor.name-modes.label.ignore-the-frame-name', - 'Ignore the frame name' - ), - }, - ]; + const frameNameMode = options.frameNameMode ?? ConcatenateFrameNameMode.FieldName; - const frameNameMode = options.frameNameMode ?? ConcatenateFrameNameMode.FieldName; - - return ( -
- - v.value === frameNameMode)} + onChange={onModeChanged} + /> + + {frameNameMode === ConcatenateFrameNameMode.Label && ( + + v.value === frameNameMode)} - onChange={this.onModeChanged} + value={options.frameNameLabel ?? ''} + placeholder={t('transformers.concatenate-transformer-editor.placeholder-frame', 'Frame')} + onChange={onLabelChanged} /> - {frameNameMode === ConcatenateFrameNameMode.Label && ( - - - - )} -
- ); - } -} + )} +
+ ); +}); +ConcatenateTransformerEditor.displayName = 'ConcatenateTransformerEditor'; export const getConcatenateTransformRegistryItem: () => TransformerRegistryItem = () => ({ diff --git a/public/app/features/variables/constant/ConstantVariableEditor.tsx b/public/app/features/variables/constant/ConstantVariableEditor.tsx index 6325ef2890c..5eb7a1f352d 100644 --- a/public/app/features/variables/constant/ConstantVariableEditor.tsx +++ b/public/app/features/variables/constant/ConstantVariableEditor.tsx @@ -1,4 +1,4 @@ -import { FormEvent, PureComponent } from 'react'; +import { FormEvent, memo } from 'react'; import { ConstantVariableModel } from '@grafana/data'; import { ConstantVariableForm } from 'app/features/dashboard-scene/settings/variables/components/ConstantVariableForm'; @@ -7,16 +7,15 @@ import { VariableEditorProps } from '../editor/types'; export interface Props extends VariableEditorProps {} -export class ConstantVariableEditor extends PureComponent { - onChange = (event: FormEvent) => { - this.props.onPropChange({ +export const ConstantVariableEditor = memo(({ variable, onPropChange }: Props) => { + const onChange = (event: FormEvent) => { + onPropChange({ propName: 'query', propValue: event.currentTarget.value, updateOptions: true, }); }; - render() { - return ; - } -} + return ; +}); +ConstantVariableEditor.displayName = 'ConstantVariableEditor'; diff --git a/public/app/features/variables/pickers/shared/VariableInput.tsx b/public/app/features/variables/pickers/shared/VariableInput.tsx index 7ff43d04657..8f577ac5da5 100644 --- a/public/app/features/variables/pickers/shared/VariableInput.tsx +++ b/public/app/features/variables/pickers/shared/VariableInput.tsx @@ -1,48 +1,45 @@ -import { PureComponent } from 'react'; -import * as React from 'react'; +import { memo, type KeyboardEvent, type HTMLProps } from 'react'; import { t } from '@grafana/i18n'; import { NavigationKey } from '../types'; -export interface Props extends Omit, 'onChange' | 'value'> { +export interface Props extends Omit, 'onChange' | 'value'> { onChange: (value: string) => void; onNavigate: (key: NavigationKey, clearOthers: boolean) => void; value: string | null; } -export class VariableInput extends PureComponent { - onKeyDown = (event: React.KeyboardEvent) => { +export const VariableInput = memo(({ value, id, onNavigate, onChange, ...restProps }: Props) => { + const onKeyDown = (event: KeyboardEvent) => { if (NavigationKey[event.keyCode] && event.keyCode !== NavigationKey.select) { const clearOthers = event.ctrlKey || event.metaKey || event.shiftKey; - this.props.onNavigate(event.keyCode, clearOthers); + onNavigate(event.keyCode, clearOthers); event.preventDefault(); } }; - onChange = (event: React.ChangeEvent) => { - this.props.onChange(event.target.value); + const handleChange = (event: React.ChangeEvent) => { + onChange(event.target.value); }; - render() { - const { value, id, onNavigate, ...restProps } = this.props; - return ( - { - if (instance) { - instance.focus(); - instance.setAttribute('style', `width:${Math.max(instance.width, 150)}px`); - } - }} - id={id} - type="text" - className="gf-form-input" - value={value ?? ''} - onChange={this.onChange} - onKeyDown={this.onKeyDown} - placeholder={t('variable.dropdown.placeholder', 'Enter variable value')} - /> - ); - } -} + return ( + { + if (instance) { + instance.focus(); + instance.setAttribute('style', `width:${Math.max(instance.width, 150)}px`); + } + }} + id={id} + type="text" + className="gf-form-input" + value={value ?? ''} + onChange={handleChange} + onKeyDown={onKeyDown} + placeholder={t('variable.dropdown.placeholder', 'Enter variable value')} + /> + ); +}); +VariableInput.displayName = 'VariableInput'; diff --git a/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx b/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx index 9889693efae..d866ad49185 100644 --- a/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx +++ b/public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx @@ -1,4 +1,4 @@ -import { PureComponent } from 'react'; +import { memo } from 'react'; import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; import { ConnectionConfig } from '@grafana/google-sdk'; @@ -10,8 +10,8 @@ import { CloudMonitoringOptions, CloudMonitoringSecureJsonData } from '../../typ export type Props = DataSourcePluginOptionsEditorProps; -export class ConfigEditor extends PureComponent { - handleOnOptionsChange = (options: Props['options']) => { +export const ConfigEditor = memo(({ options, onOptionsChange }: Props) => { + const handleOnOptionsChange = (options: Props['options']) => { if (options.jsonData.privateKeyPath || options.secureJsonFields['privateKey']) { reportInteraction('grafana_cloud_monitoring_config_changed', { authenticationType: 'JWT', @@ -19,34 +19,32 @@ export class ConfigEditor extends PureComponent { privateKeyPath: !!options.jsonData.privateKeyPath, }); } - this.props.onOptionsChange(options); + onOptionsChange(options); }; - render() { - const { options, onOptionsChange } = this.props; - return ( - <> - - - - {config.secureSocksDSProxyEnabled && ( - <> - - - - - - )} - - ); - } -} + return ( + <> + + + + {config.secureSocksDSProxyEnabled && ( + <> + + + + + + )} + + ); +}); +ConfigEditor.displayName = 'ConfigEditor'; diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/components/CSVWaveEditor.tsx b/public/app/plugins/datasource/grafana-testdata-datasource/components/CSVWaveEditor.tsx index ac2a9a2a779..1643cc0e9bd 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/components/CSVWaveEditor.tsx +++ b/public/app/plugins/datasource/grafana-testdata-datasource/components/CSVWaveEditor.tsx @@ -1,4 +1,4 @@ -import { PureComponent, useState } from 'react'; +import { memo, useState } from 'react'; import * as React from 'react'; import { Button, InlineField, InlineFieldRow, Input } from '@grafana/ui'; @@ -93,43 +93,42 @@ const CSVWaveEditor = (props: WaveProps) => { ); }; -export class CSVWavesEditor extends PureComponent { - onChange = (index: number, wave?: CSVWave) => { - let waves = [...(this.props.waves ?? defaultCSVWaveQuery)]; +export const CSVWavesEditor = memo(({ waves, onChange }: WavesProps) => { + const handleChange = (index: number, wave?: CSVWave) => { + let wavesArray = [...(waves ?? defaultCSVWaveQuery)]; if (wave) { - waves[index] = { ...wave }; + wavesArray[index] = { ...wave }; } else { // remove the element - waves.splice(index, 1); + wavesArray.splice(index, 1); } - this.props.onChange(waves); + onChange(wavesArray); }; - onAdd = () => { - const waves = [...(this.props.waves ?? defaultCSVWaveQuery)]; - waves.push({ ...defaultCSVWaveQuery[0] }); - this.props.onChange(waves); + const onAdd = () => { + const wavesArray = [...(waves ?? defaultCSVWaveQuery)]; + wavesArray.push({ ...defaultCSVWaveQuery[0] }); + onChange(wavesArray); }; - render() { - let waves = this.props.waves ?? defaultCSVWaveQuery; - if (!waves.length) { - waves = defaultCSVWaveQuery; - } - - return ( - <> - {waves.map((wave, index) => ( - - ))} - - ); + let wavesArray = waves ?? defaultCSVWaveQuery; + if (!wavesArray.length) { + wavesArray = defaultCSVWaveQuery; } -} + + return ( + <> + {wavesArray.map((wave, index) => ( + + ))} + + ); +}); +CSVWavesEditor.displayName = 'CSVWavesEditor'; diff --git a/public/app/plugins/panel/stat/StatPanel.tsx b/public/app/plugins/panel/stat/StatPanel.tsx index 6d1c35cc63a..055fc4adc93 100644 --- a/public/app/plugins/panel/stat/StatPanel.tsx +++ b/public/app/plugins/panel/stat/StatPanel.tsx @@ -1,5 +1,5 @@ import { isNumber } from 'lodash'; -import { PureComponent } from 'react'; +import { memo, useCallback } from 'react'; import { DisplayValueAlignmentFactors, @@ -12,120 +12,131 @@ import { } from '@grafana/data'; import { findNumericFieldMinMax } from '@grafana/data/internal'; import { BigValueTextMode, BigValueGraphMode } from '@grafana/schema'; -import { BigValue, DataLinksContextMenu, VizRepeater, VizRepeaterRenderValueProps } from '@grafana/ui'; +import { BigValue, DataLinksContextMenu, useTheme2, VizRepeater, VizRepeaterRenderValueProps } from '@grafana/ui'; import { DataLinksContextMenuApi } from '@grafana/ui/internal'; -import { config } from 'app/core/config'; import { Options } from './panelcfg.gen'; -export class StatPanel extends PureComponent> { - renderComponent = ( - valueProps: VizRepeaterRenderValueProps, - menuProps: DataLinksContextMenuApi - ): JSX.Element => { - const { timeRange, options } = this.props; - const { value, alignmentFactors, width, height, count } = valueProps; - const { openMenu, targetClassName } = menuProps; - let sparkline = value.sparkline; - if (sparkline) { - sparkline.timeRange = timeRange; - } +export const StatPanel = memo( + ({ + timeRange, + options, + fieldConfig, + title, + data, + replaceVariables, + timeZone, + height, + width, + renderCounter, + }: PanelProps) => { + const theme = useTheme2(); - return ( - + const getTextMode = useCallback(() => { + // If we have manually set displayName or panel title switch text mode to value and name + if (options.textMode === BigValueTextMode.Auto && (fieldConfig.defaults.displayName || !title)) { + return BigValueTextMode.ValueAndName; + } + + return options.textMode; + }, [options.textMode, fieldConfig.defaults.displayName, title]); + + const renderComponent = useCallback( + ( + valueProps: VizRepeaterRenderValueProps, + menuProps: DataLinksContextMenuApi + ): JSX.Element => { + const { value, alignmentFactors, width, height, count } = valueProps; + const { openMenu, targetClassName } = menuProps; + let sparkline = value.sparkline; + if (sparkline) { + sparkline.timeRange = timeRange; + } + + return ( + + ); + }, + [theme, timeRange, options, getTextMode] ); - }; - getTextMode() { - const { options, fieldConfig, title } = this.props; + const renderValue = useCallback( + (valueProps: VizRepeaterRenderValueProps): JSX.Element => { + const { value } = valueProps; + const { getLinks, hasLinks } = value; - // If we have manually set displayName or panel title switch text mode to value and name - if (options.textMode === BigValueTextMode.Auto && (fieldConfig.defaults.displayName || !title)) { - return BigValueTextMode.ValueAndName; - } + if (hasLinks && getLinks) { + return ( + + {(api) => { + return renderComponent(valueProps, api); + }} + + ); + } - return options.textMode; - } + return renderComponent(valueProps, {}); + }, + [renderComponent] + ); - renderValue = (valueProps: VizRepeaterRenderValueProps): JSX.Element => { - const { value } = valueProps; - const { getLinks, hasLinks } = value; + const getValues = useCallback((): FieldDisplay[] => { + let globalRange: NumericRange | undefined = undefined; - if (hasLinks && getLinks) { - return ( - - {(api) => { - return this.renderComponent(valueProps, api); - }} - - ); - } - - return this.renderComponent(valueProps, {}); - }; - - getValues = (): FieldDisplay[] => { - const { data, options, replaceVariables, fieldConfig, timeZone } = this.props; - - let globalRange: NumericRange | undefined = undefined; - - for (let frame of data.series) { - for (let field of frame.fields) { - let { config } = field; - // mostly copied from fieldOverrides, since they are skipped during streaming - // Set the Min/Max value automatically - if (field.type === FieldType.number) { - if (field.state?.range) { - continue; + for (let frame of data.series) { + for (let field of frame.fields) { + let { config } = field; + // mostly copied from fieldOverrides, since they are skipped during streaming + // Set the Min/Max value automatically + if (field.type === FieldType.number) { + if (field.state?.range) { + continue; + } + if (!globalRange && (!isNumber(config.min) || !isNumber(config.max))) { + globalRange = findNumericFieldMinMax(data.series); + } + const min = config.min ?? globalRange!.min; + const max = config.max ?? globalRange!.max; + field.state = field.state ?? {}; + field.state.range = { min, max, delta: max! - min! }; } - if (!globalRange && (!isNumber(config.min) || !isNumber(config.max))) { - globalRange = findNumericFieldMinMax(data.series); - } - const min = config.min ?? globalRange!.min; - const max = config.max ?? globalRange!.max; - field.state = field.state ?? {}; - field.state.range = { min, max, delta: max! - min! }; } } - } - return getFieldDisplayValues({ - fieldConfig, - reduceOptions: options.reduceOptions, - replaceVariables, - theme: config.theme2, - data: data.series, - sparkline: options.graphMode !== BigValueGraphMode.None, - percentChange: options.showPercentChange, - timeZone, - }); - }; - - render() { - const { height, options, width, data, renderCounter } = this.props; + return getFieldDisplayValues({ + fieldConfig, + reduceOptions: options.reduceOptions, + replaceVariables, + theme, + data: data.series, + sparkline: options.graphMode !== BigValueGraphMode.None, + percentChange: options.showPercentChange, + timeZone, + }); + }, [data, fieldConfig, theme, options, replaceVariables, timeZone]); return ( > { /> ); } -} +); +StatPanel.displayName = 'StatPanel'; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index dcbf60af7b5..2d5bd426837 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -8782,6 +8782,9 @@ "drawer": { "close": "Close" }, + "error-boundary": { + "title": "An unexpected error happened" + }, "feature-badge": { "experimental": "Experimental", "new": "New!", From d399f116b805ba210e09040e5fdc6c008a0e9b5e Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Thu, 30 Oct 2025 10:28:15 +0100 Subject: [PATCH 39/72] Alerting: Improve instance details drawer in Alerts (#113106) * Add alert instance breadcrumbs, change instance drawer title * Update translations * Add instance drawer title component and unify its usage --- .../InstanceDetailsDrawer.tsx | 37 +++++++++++++----- .../InstanceDetailsDrawerTitle.tsx | 39 +++++++++++++++++++ public/locales/en-US/grafana.json | 7 ++-- 3 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawerTitle.tsx diff --git a/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx index 704c4d54c47..7ce7e9ae6f7 100644 --- a/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx +++ b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx @@ -3,12 +3,11 @@ 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 { Alert, Box, Drawer, Icon, LoadingBar, LoadingPlaceholder, Stack, Text, useStyles2 } from '@grafana/ui'; import { AlertQuery, GrafanaRuleDefinition } from 'app/types/unified-alerting-dto'; import { alertRuleApi } from '../../api/alertRuleApi'; @@ -19,6 +18,7 @@ import { LogRecord, historyDataFrameToLogRecords } from '../../components/rules/ import { isAlertQueryOfAlertData } from '../../rule-editor/formProcessing'; import { stringifyErrorLike } from '../../utils/misc'; +import { InstanceDetailsDrawerTitle } from './InstanceDetailsDrawerTitle'; import { QueryVisualization } from './QueryVisualization'; import { convertStateHistoryToAnnotations } from './stateHistoryUtils'; @@ -66,7 +66,7 @@ export function InstanceDetailsDrawer({ ruleUID, instanceLabels, onClose }: Inst if (error) { return ( - + } onClose={onClose} size="md"> ); @@ -74,15 +74,15 @@ export function InstanceDetailsDrawer({ ruleUID, instanceLabels, onClose }: Inst if (loading || !rule) { return ( - -
{t('alerting.common.loading', 'Loading...')}
+ } onClose={onClose} size="md"> + ); } return ( } onClose={onClose} size="lg" > @@ -106,10 +106,6 @@ export function InstanceDetailsDrawer({ ruleUID, instanceLabels, onClose }: Inst )} - - - - {t('alerting.instance-details.state-history', 'Recent State Changes')} {stateHistoryFetching && } @@ -139,6 +135,27 @@ export function InstanceDetailsDrawer({ ruleUID, instanceLabels, onClose }: Inst ); } +export interface InstanceLocationProps { + folderTitle: string; + groupName: string; + ruleName: string; +} + +export function InstanceLocation({ folderTitle, groupName, ruleName }: InstanceLocationProps) { + return ( + + + + {folderTitle} + + {groupName} + + {ruleName} + + + ); +} + function extractQueryDetails(rule: GrafanaRuleDefinition) { const dataQueries = rule.data.filter((query: AlertQuery) => isAlertQueryOfAlertData(query)); diff --git a/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawerTitle.tsx b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawerTitle.tsx new file mode 100644 index 00000000000..1b07cdb284f --- /dev/null +++ b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawerTitle.tsx @@ -0,0 +1,39 @@ +import { AlertLabels } from '@grafana/alerting/unstable'; +import { Labels } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { Box, Stack, Text } from '@grafana/ui'; +import { GrafanaRuleDefinition } from 'app/types/unified-alerting-dto'; + +import { stringifyFolder, useFolder } from '../../hooks/useFolder'; + +import { InstanceLocation } from './InstanceDetailsDrawer'; + +interface InstanceDetailsDrawerTitleProps { + instanceLabels: Labels; + rule?: GrafanaRuleDefinition; +} + +export function InstanceDetailsDrawerTitle({ instanceLabels, rule }: InstanceDetailsDrawerTitleProps) { + const { folder } = useFolder(rule?.namespace_uid); + + return ( + + + Instance details + + + + {Object.keys(instanceLabels).length > 0 ? ( + + ) : ( + {t('alerting.triage.no-labels', 'No labels')} + )} + + + + {folder && rule && ( + + )} + + ); +} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 2d5bd426837..b687292e3ad 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1662,9 +1662,6 @@ "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" @@ -2950,7 +2947,9 @@ "alert-instances": "Alert instances", "error-loading-rule": "Error loading rule", "firing-instances-count": "{{firingCount}} firing instances", - "instance-details": "Instance Details", + "instance-details-drawer": { + "instance-details": "Instance details" + }, "no-instances-found": "No alert instances found for rule: {{ruleUID}}", "no-labels": "No labels", "open-in-sidebar": "Open in sidebar", From ee62a8d431c00e510b1664f899d9bbef06f11ba2 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Thu, 30 Oct 2025 10:43:13 +0100 Subject: [PATCH 40/72] Alerting: Alerts page improvements (#113172) * Remove column header from Triage workbench * Use md size for details Drawers * Fix top workbench border * Fix query filter for alert instances * Remove the default grouping * Update translations --- .../alerting/unified/triage/Timeline.tsx | 37 ------------------- .../alerting/unified/triage/Workbench.tsx | 9 ----- .../InstanceDetailsDrawer.tsx | 2 +- .../unified/triage/rows/GenericRow.tsx | 5 +-- .../triage/rule-details/RuleDetailsDrawer.tsx | 7 ++-- .../triage/scene/AlertRuleInstances.tsx | 6 ++- .../unified/triage/scene/TriageScene.tsx | 1 - public/locales/en-US/grafana.json | 3 -- 8 files changed, 9 insertions(+), 61 deletions(-) delete mode 100644 public/app/features/alerting/unified/triage/Timeline.tsx diff --git a/public/app/features/alerting/unified/triage/Timeline.tsx b/public/app/features/alerting/unified/triage/Timeline.tsx deleted file mode 100644 index 3bde44c91b9..00000000000 --- a/public/app/features/alerting/unified/triage/Timeline.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { scaleTime } from 'd3-scale'; -import { useMemo } from 'react'; -import { useMeasure } from 'react-use'; - -import { Stack, Text } from '@grafana/ui'; - -import { Domain } from './types'; - -interface TimelineProps { - domain: Domain; -} - -export const TimelineHeader = ({ domain }: TimelineProps) => { - const [ref, { width }] = useMeasure(); - - const ticks = useMemo(() => { - const xScale = scaleTime().domain(domain).range([0, width]).nice(0); - const tickFormatter = xScale.tickFormat(); - - return xScale.ticks(5).map((value) => ({ - value: tickFormatter(value), - xOffset: xScale(value), - })); - }, [domain, width]); - - return ( -
- - {ticks.map((tick) => ( - - {tick.value} - - ))} - -
- ); -}; diff --git a/public/app/features/alerting/unified/triage/Workbench.tsx b/public/app/features/alerting/unified/triage/Workbench.tsx index 85a4788a706..ceb4196ad7f 100644 --- a/public/app/features/alerting/unified/triage/Workbench.tsx +++ b/public/app/features/alerting/unified/triage/Workbench.tsx @@ -4,15 +4,12 @@ import { useState } from 'react'; import { useMeasure } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; -import { t } from '@grafana/i18n'; import { SceneQueryRunner } from '@grafana/scenes'; import { ScrollContainer, useSplitter, useStyles2 } from '@grafana/ui'; import { DEFAULT_PER_PAGE_PAGINATION } from 'app/core/constants'; -import { EditorColumnHeader } from '../components/EditorColumnHeader'; import LoadMoreHelper from '../rule-list/LoadMoreHelper'; -import { TimelineHeader } from './Timeline'; import { WorkbenchProvider } from './WorkbenchContext'; import { AlertRuleRow } from './rows/AlertRuleRow'; import { FolderGroupRow } from './rows/FolderGroupRow'; @@ -141,12 +138,6 @@ export function Workbench({ domain, data, queryRunner }: WorkbenchProps) { -
- - - - -
{/* Render actual data */}
diff --git a/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx index 7ce7e9ae6f7..617f097ce89 100644 --- a/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx +++ b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx @@ -84,7 +84,7 @@ export function InstanceDetailsDrawer({ ruleUID, instanceLabels, onClose }: Inst } onClose={onClose} - size="lg" + size="md" > diff --git a/public/app/features/alerting/unified/triage/rows/GenericRow.tsx b/public/app/features/alerting/unified/triage/rows/GenericRow.tsx index 904687a4dbf..edc475fbfe1 100644 --- a/public/app/features/alerting/unified/triage/rows/GenericRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/GenericRow.tsx @@ -108,10 +108,7 @@ export const getStyles = (theme: GrafanaTheme2) => { display: 'flex', position: 'relative', flexBasis: 0, - border: 'solid 1px transparent', - borderBottom: `1px solid ${theme.colors.border.medium}`, - borderLeft: `1px solid ${theme.colors.border.medium}`, - borderRight: `1px solid ${theme.colors.border.medium}`, + border: `1px solid ${theme.colors.border.medium}`, }), leftColumn: css({ overflow: 'hidden', diff --git a/public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx b/public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx index 69687456341..980f8272f43 100644 --- a/public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx +++ b/public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx @@ -48,7 +48,7 @@ export function RuleDetailsDrawer({ ruleUID, onClose }: RuleDetailsDrawerProps) if (error) { return ( - + ); @@ -56,7 +56,7 @@ export function RuleDetailsDrawer({ ruleUID, onClose }: RuleDetailsDrawerProps) if (loading || !rule) { return ( - +
{t('alerting.common.loading', 'Loading...')}
); @@ -69,7 +69,6 @@ export function RuleDetailsDrawer({ ruleUID, onClose }: RuleDetailsDrawerProps) return ( @@ -104,7 +103,7 @@ export function RuleDetailsDrawer({ ruleUID, onClose }: RuleDetailsDrawerProps) {t('alerting.triage.rule-details.subtitle', 'Rule details and conditions')} } - size="lg" + size="md" tabs={ Date: Thu, 30 Oct 2025 10:49:54 +0100 Subject: [PATCH 41/72] PanelEditor: Fixes double top border around Queries (#112865) * PanelEditor: Fixes double top border around Queries * Fixes --- .../PanelDataPane/PanelDataPane.tsx | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx index 943637d2722..4e7d10fa22f 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx @@ -80,14 +80,16 @@ function PanelDataPaneRendered({ model }: SceneComponentProps) { return (
- + {tabs.map((t) => t.renderTab({ active: t.tabId === tab, onChangeTab: () => model.onChangeTab(t) }))} - - - {currentTab && } - - +
+ + + {currentTab && } + + +
); } @@ -116,13 +118,18 @@ function getStyles(theme: GrafanaTheme2) { height: '100%', width: '100%', }), - tabContent: css({ - padding: theme.spacing(2), + tabBorder: css({ + background: theme.colors.background.primary, border: `1px solid ${theme.colors.border.weak}`, borderLeft: 'none', borderBottom: 'none', borderTopRightRadius: theme.shape.radius.default, flexGrow: 1, + overflow: 'hidden', + }), + tabContent: css({ + padding: theme.spacing(2), + height: '100%', }), tabsBar: css({ flexShrink: 0, From 344fc5606fa27146613d5406628a95ccd70808a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 30 Oct 2025 10:50:05 +0100 Subject: [PATCH 42/72] PanelTimeCompare: Support saving time compare window (#113150) * PanelTimeCompare: Support saving time compare window * fix indentation * Fix merge issue * Update * Update * make gen-cue --------- Co-authored-by: oscarkilhed --- apps/dashboard/kinds/v2beta1/dashboard_spec.cue | 1 + .../pkg/apis/dashboard/v0alpha1/dashboard_kind.cue | 4 ++++ .../dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue | 4 ++++ .../dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue | 1 + .../pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go | 1 + .../pkg/apis/dashboard/v2beta1/zz_generated.openapi.go | 6 ++++++ kinds/dashboard/dashboard_kind.cue | 4 ++++ .../src/raw/dashboard/x/dashboard_types.gen.ts | 5 +++++ .../src/schema/dashboard/v2alpha0/dashboard.schema.cue | 1 + .../src/schema/dashboard/v2beta1/types.spec.gen.ts | 1 + pkg/kinds/dashboard/dashboard_spec_gen.go | 3 +++ .../serialization/transformSaveModelToScene.ts | 3 ++- .../serialization/transformSceneToSaveModel.ts | 1 + .../serialization/transformSceneToSaveModelSchemaV2.ts | 1 + public/app/features/dashboard/state/PanelModel.ts | 1 + 15 files changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/dashboard/kinds/v2beta1/dashboard_spec.cue b/apps/dashboard/kinds/v2beta1/dashboard_spec.cue index f0278fc431c..ef1490091c2 100644 --- a/apps/dashboard/kinds/v2beta1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2beta1/dashboard_spec.cue @@ -452,6 +452,7 @@ QueryOptionsSpec: { interval?: string cacheTimeout?: string hideTimeOverride?: bool + timeCompare?: string } DataQueryKind: { diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue index 526ddba3bfc..7d8a0e551bf 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue @@ -670,6 +670,10 @@ lineage: schemas: [{ // Controls if the timeFrom or timeShift overrides are shown in the panel header hideTimeOverride?: bool + // Compare the current time range with a previous period + // For example "1d" to compare current period but shifted back 1 day + timeCompare?: string + // Dynamically load the panel libraryPanel?: #LibraryPanelRef diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue index 526ddba3bfc..7d8a0e551bf 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue @@ -670,6 +670,10 @@ lineage: schemas: [{ // Controls if the timeFrom or timeShift overrides are shown in the panel header hideTimeOverride?: bool + // Compare the current time range with a previous period + // For example "1d" to compare current period but shifted back 1 day + timeCompare?: string + // Dynamically load the panel libraryPanel?: #LibraryPanelRef diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue index fb46afb5c76..31603c6240b 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue @@ -456,6 +456,7 @@ QueryOptionsSpec: { interval?: string cacheTimeout?: string hideTimeOverride?: bool + timeCompare?: string } DataQueryKind: { diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go index 35674997511..4229e5765d1 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go @@ -278,6 +278,7 @@ type DashboardQueryOptionsSpec struct { Interval *string `json:"interval,omitempty"` CacheTimeout *string `json:"cacheTimeout,omitempty"` HideTimeOverride *bool `json:"hideTimeOverride,omitempty"` + TimeCompare *string `json:"timeCompare,omitempty"` } // NewDashboardQueryOptionsSpec creates a new DashboardQueryOptionsSpec object. diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go index 5e51134e80c..97fb1c10326 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go @@ -3426,6 +3426,12 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardQueryOptionsSpec(ref common.Refe Format: "", }, }, + "timeCompare": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, diff --git a/kinds/dashboard/dashboard_kind.cue b/kinds/dashboard/dashboard_kind.cue index 13c2b4f25f6..e32b8b6725e 100644 --- a/kinds/dashboard/dashboard_kind.cue +++ b/kinds/dashboard/dashboard_kind.cue @@ -666,6 +666,10 @@ lineage: schemas: [{ // Controls if the timeFrom or timeShift overrides are shown in the panel header hideTimeOverride?: bool + // Compare the current time range with a previous period + // For example "1d" to compare current period but shifted back 1 day + timeCompare?: string + // Dynamically load the panel libraryPanel?: #LibraryPanelRef diff --git a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts index b370fc6206c..973f10bfb21 100644 --- a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts +++ b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts @@ -885,6 +885,11 @@ export interface Panel { * Depends on the panel plugin. See the plugin documentation for details. */ targets?: Array>; + /** + * Compare the current time range with a previous period + * For example "1d" to compare current period but shifted back 1 day + */ + timeCompare?: string; /** * Overrides the relative time range for individual panels, * which causes them to be different than what is selected in diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index 5369503d6f2..ba637656ed8 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -409,6 +409,7 @@ QueryOptionsSpec: { interval?: string cacheTimeout?: string hideTimeOverride?: bool + timeCompare?: string } DataQueryKind: { diff --git a/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts index 19c324f1cd9..e9cbe9e649a 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts @@ -219,6 +219,7 @@ export interface QueryOptionsSpec { interval?: string; cacheTimeout?: string; hideTimeOverride?: boolean; + timeCompare?: string; } export const defaultQueryOptionsSpec = (): QueryOptionsSpec => ({ diff --git a/pkg/kinds/dashboard/dashboard_spec_gen.go b/pkg/kinds/dashboard/dashboard_spec_gen.go index 2ebe81760c9..43877fbca7b 100644 --- a/pkg/kinds/dashboard/dashboard_spec_gen.go +++ b/pkg/kinds/dashboard/dashboard_spec_gen.go @@ -191,6 +191,9 @@ type Panel struct { TimeShift *string `json:"timeShift,omitempty"` // Controls if the timeFrom or timeShift overrides are shown in the panel header HideTimeOverride *bool `json:"hideTimeOverride,omitempty"` + // Compare the current time range with a previous period + // For example "1d" to compare current period but shifted back 1 day + TimeCompare *string `json:"timeCompare,omitempty"` // Dynamically load the panel LibraryPanel *LibraryPanelRef `json:"libraryPanel,omitempty"` // Sets panel queries cache timeout. diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index aab1f181854..7646b7df68c 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -456,11 +456,12 @@ export function buildGridItemForPanel(panel: PanelModel): DashboardGridItem { }); } - if (panel.timeFrom || panel.timeShift) { + if (panel.timeFrom || panel.timeShift || panel.timeCompare) { vizPanelState.$timeRange = new PanelTimeRange({ timeFrom: panel.timeFrom, timeShift: panel.timeShift, hideTimeOverride: panel.hideTimeOverride, + compareWith: panel.timeCompare, }); } diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts index 16b3957435f..bd5a1139848 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts @@ -232,6 +232,7 @@ export function vizPanelToPanel( panel.timeFrom = panelTime.state.timeFrom; panel.timeShift = panelTime.state.timeShift; panel.hideTimeOverride = panelTime.state.hideTimeOverride; + panel.timeCompare = panelTime.state.compareWith; } if (gridItem instanceof DashboardGridItem) { diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index da7e19d3a41..85cd9e8ee7b 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -387,6 +387,7 @@ function getVizPanelQueryOptions(vizPanel: VizPanel): QueryOptionsSpec { queryOptions.timeFrom = panelTime.state.timeFrom; queryOptions.timeShift = panelTime.state.timeShift; queryOptions.hideTimeOverride = panelTime.state.hideTimeOverride; + queryOptions.timeCompare = panelTime.state.compareWith; } return queryOptions; } diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 7e67a270ed4..4b06df7c8bc 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -170,6 +170,7 @@ export class PanelModel implements DataConfigSource, IPanelModel { timeFrom?: any; timeShift?: any; hideTimeOverride?: boolean; + timeCompare?: string; declare options: { [key: string]: any; }; From cb86be2e32d0aef062e3d313addf99ff156e8cf3 Mon Sep 17 00:00:00 2001 From: maicon Date: Thu, 30 Oct 2025 07:51:31 -0300 Subject: [PATCH 43/72] Unistore: ensure dashboard DeleteInFolders work on both storages (#113197) Signed-off-by: Maicon Costa --- .../dashboards/service/dashboard_service.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 3d26169edd7..f59416924de 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -1638,12 +1638,14 @@ func (dr *DashboardServiceImpl) DeleteInFolders(ctx context.Context, orgID int64 defer span.End() // We need a list of dashboard uids inside the folder to delete related public dashboards - dashes, err := dr.dashboardStore.FindDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{ + dashes, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{ SignedInUser: u, - FolderUIDs: folderUIDs, - OrgId: orgID, Type: searchstore.TypeDashboard, + Limit: 100000, + OrgId: orgID, + FolderUIDs: folderUIDs, }) + if err != nil { return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err) } @@ -1658,7 +1660,14 @@ func (dr *DashboardServiceImpl) DeleteInFolders(ctx context.Context, orgID int64 return err } - return dr.dashboardStore.DeleteDashboardsInFolders(ctx, &dashboards.DeleteDashboardsInFolderRequest{FolderUIDs: folderUIDs, OrgID: orgID}) + for _, dash := range dashes { + errDel := dr.DeleteDashboard(ctx, dash.ID, dash.UID, orgID) + if errDel != nil { + dr.log.Error("failed to delete dashboard inside folder", "dashboardUID", dash.UID, "folderUIDs", folderUIDs, "error", errDel) + } + } + + return err } func (dr *DashboardServiceImpl) Kind() string { return entity.StandardKindDashboard } From bbfb8268d1502bb33dbe7cd7bcf07c4f79fead68 Mon Sep 17 00:00:00 2001 From: Costa Alexoglou Date: Thu, 30 Oct 2025 11:55:36 +0100 Subject: [PATCH 44/72] Provisioning: concurrent deletes in finalizers and 404 handling (#113155) * fix: concurrent deletes in finalizers and 404 handling * chore: feedback review * fix: broken tests --- .../apis/dashboard/cuevalidator/validator.go | 29 +++ .../pkg/apis/dashboard/v0alpha1/validation.go | 15 +- .../pkg/apis/dashboard/v1beta1/validation.go | 15 +- .../pkg/apis/dashboard/v2alpha1/validation.go | 16 +- .../pkg/apis/dashboard/v2beta1/validation.go | 16 +- pkg/operators/provisioning/repo_operator.go | 3 + .../provisioning/controller/finalizers.go | 64 +++++- .../controller/finalizers_test.go | 182 ++++++++++++++++-- .../provisioning/controller/repository.go | 2 + pkg/registry/apis/provisioning/register.go | 1 + 10 files changed, 290 insertions(+), 53 deletions(-) create mode 100644 apps/dashboard/pkg/apis/dashboard/cuevalidator/validator.go diff --git a/apps/dashboard/pkg/apis/dashboard/cuevalidator/validator.go b/apps/dashboard/pkg/apis/dashboard/cuevalidator/validator.go new file mode 100644 index 00000000000..38792ade026 --- /dev/null +++ b/apps/dashboard/pkg/apis/dashboard/cuevalidator/validator.go @@ -0,0 +1,29 @@ +package cuevalidator + +import ( + "sync" + + "cuelang.org/go/cue" + cuejson "cuelang.org/go/encoding/json" +) + +// Validator provides thread-safe CUE schema validation. +// +// CUE is not safe for concurrent use: https://github.com/cue-lang/cue/discussions/1205#discussioncomment-1189238 +// This validator uses a mutex to protect concurrent access to the underlying CUE validation. +type Validator struct { + schema cue.Value + mu sync.Mutex +} + +func NewValidator(schema cue.Value) *Validator { + return &Validator{ + schema: schema, + } +} + +func (v *Validator) Validate(data []byte) error { + v.mu.Lock() + defer v.mu.Unlock() + return cuejson.Validate(data, v.schema) +} diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go index 6fdf1dd4515..7c5573f600a 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go @@ -7,13 +7,13 @@ import ( "strings" "sync" + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/cuevalidator" "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" "k8s.io/apimachinery/pkg/util/validation/field" "cuelang.org/go/cue" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" - cuejson "cuelang.org/go/encoding/json" ) func ValidateDashboardSpec(obj *Dashboard, forceValidation bool) (field.ErrorList, field.ErrorList) { @@ -33,7 +33,7 @@ func ValidateDashboardSpec(obj *Dashboard, forceValidation bool) (field.ErrorLis }, schemaVersionError } - if err := cuejson.Validate(data, getCueSchema()); err != nil { + if err := getValidator().Validate(data); err != nil { errs := field.ErrorList{} for _, e := range errors.Errors(err) { @@ -71,20 +71,21 @@ func formatErrorPath(path []string) string { } var ( - compiledSchema cue.Value - getSchemaOnce sync.Once + validator *cuevalidator.Validator + getSchemaOnce sync.Once ) //go:embed dashboard_kind.cue var schemaSource string -func getCueSchema() cue.Value { +func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { cueCtx := cuecontext.New() - compiledSchema = cueCtx.CompileString(schemaSource).LookupPath( + compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( cue.ParsePath("lineage.schemas[0].schema.spec"), ) + validator = cuevalidator.NewValidator(compiledSchema) }) - return compiledSchema + return validator } diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go index 7020aa46b90..b48e38ffd7b 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go @@ -12,8 +12,8 @@ import ( "cuelang.org/go/cue" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" - cuejson "cuelang.org/go/encoding/json" + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/cuevalidator" "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" ) @@ -34,7 +34,7 @@ func ValidateDashboardSpec(obj *Dashboard, forceValidation bool) (field.ErrorLis }, schemaVersionError } - if err := cuejson.Validate(data, getCueSchema()); err != nil { + if err := getValidator().Validate(data); err != nil { errs := field.ErrorList{} for _, e := range errors.Errors(err) { @@ -72,20 +72,21 @@ func formatErrorPath(path []string) string { } var ( - compiledSchema cue.Value - getSchemaOnce sync.Once + validator *cuevalidator.Validator + getSchemaOnce sync.Once ) //go:embed dashboard_kind.cue var schemaSource string -func getCueSchema() cue.Value { +func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { cueCtx := cuecontext.New() - compiledSchema = cueCtx.CompileString(schemaSource).LookupPath( + compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( cue.ParsePath("lineage.schemas[0].schema.spec"), ) + validator = cuevalidator.NewValidator(compiledSchema) }) - return compiledSchema + return validator } diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go index 7c61faa8924..ca9dcd3e514 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go @@ -12,7 +12,8 @@ import ( "cuelang.org/go/cue" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" - cuejson "cuelang.org/go/encoding/json" + + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/cuevalidator" ) func ValidateDashboardSpec(obj *Dashboard) field.ErrorList { @@ -26,7 +27,7 @@ func ValidateDashboardSpec(obj *Dashboard) field.ErrorList { // Custom validation for action query params and headers validateAndTrimActionArrays(obj) - if err := cuejson.Validate(data, getCueSchema()); err != nil { + if err := getValidator().Validate(data); err != nil { errs := field.ErrorList{} for _, e := range errors.Errors(err) { @@ -123,20 +124,21 @@ func formatErrorPath(path []string) string { } var ( - compiledSchema cue.Value - getSchemaOnce sync.Once + validator *cuevalidator.Validator + getSchemaOnce sync.Once ) //go:embed dashboard_spec.cue var schemaSource string -func getCueSchema() cue.Value { +func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { cueCtx := cuecontext.New() - compiledSchema = cueCtx.CompileString(schemaSource).LookupPath( + compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( cue.ParsePath("DashboardSpec"), ) + validator = cuevalidator.NewValidator(compiledSchema) }) - return compiledSchema + return validator } diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go index 7c859626b98..518c133bcd7 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go @@ -12,7 +12,8 @@ import ( "cuelang.org/go/cue" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" - cuejson "cuelang.org/go/encoding/json" + + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/cuevalidator" ) func ValidateDashboardSpec(obj *Dashboard) field.ErrorList { @@ -26,7 +27,7 @@ func ValidateDashboardSpec(obj *Dashboard) field.ErrorList { // Custom validation for action query params and headers validateAndTrimActionArrays(obj) - if err := cuejson.Validate(data, getCueSchema()); err != nil { + if err := getValidator().Validate(data); err != nil { errs := field.ErrorList{} for _, e := range errors.Errors(err) { @@ -123,20 +124,21 @@ func formatErrorPath(path []string) string { } var ( - compiledSchema cue.Value - getSchemaOnce sync.Once + validator *cuevalidator.Validator + getSchemaOnce sync.Once ) //go:embed dashboard_spec.cue var schemaSource string -func getCueSchema() cue.Value { +func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { cueCtx := cuecontext.New() - compiledSchema = cueCtx.CompileString(schemaSource).LookupPath( + compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( cue.ParsePath("DashboardSpec"), ) + validator = cuevalidator.NewValidator(compiledSchema) }) - return compiledSchema + return validator } diff --git a/pkg/operators/provisioning/repo_operator.go b/pkg/operators/provisioning/repo_operator.go index f96653a1d87..1651a126ffe 100644 --- a/pkg/operators/provisioning/repo_operator.go +++ b/pkg/operators/provisioning/repo_operator.go @@ -90,6 +90,7 @@ func RunRepoController(deps server.OperatorDependencies) error { statusPatcher, deps.Registerer, tracer, + controllerCfg.parallelOperations, ) if err != nil { return fmt.Errorf("failed to create repository controller: %w", err) @@ -107,6 +108,7 @@ func RunRepoController(deps server.OperatorDependencies) error { type repoControllerConfig struct { provisioningControllerConfig workerCount int + parallelOperations int allowedTargets []string allowImageRendering bool minSyncInterval time.Duration @@ -128,6 +130,7 @@ func getRepoControllerConfig(cfg *setting.Cfg, registry prometheus.Registerer) ( provisioningControllerConfig: *controllerCfg, allowedTargets: allowedTargets, workerCount: cfg.SectionWithEnvOverrides("operator").Key("worker_count").MustInt(1), + parallelOperations: cfg.SectionWithEnvOverrides("operator").Key("parallel_operations").MustInt(10), allowImageRendering: cfg.SectionWithEnvOverrides("provisioning").Key("allow_image_rendering").MustBool(false), minSyncInterval: cfg.SectionWithEnvOverrides("provisioning").Key("min_sync_interval").MustDuration(1 * time.Minute), }, nil diff --git a/pkg/registry/apis/provisioning/controller/finalizers.go b/pkg/registry/apis/provisioning/controller/finalizers.go index d9c41ee4c15..5bd468fe660 100644 --- a/pkg/registry/apis/provisioning/controller/finalizers.go +++ b/pkg/registry/apis/provisioning/controller/finalizers.go @@ -7,8 +7,11 @@ import ( "slices" "sort" "strings" + "sync/atomic" "time" + "github.com/grafana/dskit/concurrency" + "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" @@ -27,6 +30,7 @@ type finalizer struct { lister resources.ResourceLister clientFactory resources.ClientFactory metrics *finalizerMetrics + maxWorkers int } func (f *finalizer) process(ctx context.Context, @@ -113,27 +117,73 @@ func (f *finalizer) processExistingItems( // Safe deletion order sortResourceListForDeletion(items) - count := 0 + var dashboards, folderItems []*provisioning.ResourceListItem for _, item := range items.Items { - res, _, err := clients.ForResource(ctx, schema.GroupVersionResource{ + if item.Group == folders.GroupVersion.Group { + folderItems = append(folderItems, &item) + } else { + dashboards = append(dashboards, &item) + } + } + + processItem := func(jobCtx context.Context, item *provisioning.ResourceListItem) error { + res, _, err := clients.ForResource(jobCtx, schema.GroupVersionResource{ Group: item.Group, Resource: item.Resource, }) if err != nil { logger.Error("error getting client for resource", "resource", item.Resource, "error", err) - return count, err + return err } - err = cb(res, &item) + err = cb(res, item) if err != nil { + if errors.IsNotFound(err) { + logger.Info("resource not found, skipping", "name", item.Name, "group", item.Group, "resource", item.Resource) + return nil + } logger.Error("error processing item", "name", item.Name, "error", err) - return count, fmt.Errorf("processing item: %w", err) - } else { + return fmt.Errorf("processing item: %w", err) + } + return nil + } + + processGroup := func(group []*provisioning.ResourceListItem) (int, error) { + var processed int64 + err := concurrency.ForEachJob(ctx, len(group), f.maxWorkers, func(ctx context.Context, idx int) error { + jobCtx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + item := group[idx] + if err := processItem(jobCtx, item); err != nil { + return err + } + atomic.AddInt64(&processed, 1) + return nil + }) + return int(processed), err + } + + count := 0 + + if len(dashboards) > 0 { + processed, err := processGroup(dashboards) + if err != nil { + return processed, err + } + count += processed + } + + if len(folderItems) > 0 { + for _, item := range folderItems { + if err := processItem(ctx, item); err != nil { + return count, err + } count++ } } - logger.Info("processed orphan items", "items", count) + + logger.Info("processed items", "items", count) return count, nil } diff --git a/pkg/registry/apis/provisioning/controller/finalizers_test.go b/pkg/registry/apis/provisioning/controller/finalizers_test.go index 140363a0507..4df89107bbe 100644 --- a/pkg/registry/apis/provisioning/controller/finalizers_test.go +++ b/pkg/registry/apis/provisioning/controller/finalizers_test.go @@ -2,10 +2,15 @@ package controller import ( "context" + "fmt" + "sync" + "sync/atomic" "testing" + "time" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" + mock "github.com/stretchr/testify/mock" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -14,6 +19,8 @@ import ( "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/dynamic" + "github.com/grafana/grafana-app-sdk/logging" + folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/apps/provisioning/pkg/repository" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" @@ -140,7 +147,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(&provisioning.ResourceList{ Items: []provisioning.ResourceListItem{ @@ -164,12 +171,12 @@ func TestFinalizer_process(t *testing.T) { } clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) clients. - On("ForResource", context.Background(), schema.GroupVersionResource{ + On("ForResource", mock.Anything, schema.GroupVersionResource{ Group: "dashboard.grafana.app", Resource: "dashboards", }). @@ -196,7 +203,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(&provisioning.ResourceList{ Items: []provisioning.ResourceListItem{ @@ -220,12 +227,12 @@ func TestFinalizer_process(t *testing.T) { } clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) clients. - On("ForResource", context.Background(), schema.GroupVersionResource{ + On("ForResource", mock.Anything, schema.GroupVersionResource{ Group: "dashboard.grafana.app", Resource: "dashboards", }). @@ -253,7 +260,7 @@ func TestFinalizer_process(t *testing.T) { clientFactory := resources.NewMockClientFactory(t) clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(nil, assert.AnError) @@ -275,7 +282,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(nil, assert.AnError) @@ -286,7 +293,7 @@ func TestFinalizer_process(t *testing.T) { clients := resources.NewMockResourceClients(t) clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) @@ -308,7 +315,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(&provisioning.ResourceList{ Items: []provisioning.ResourceListItem{ @@ -327,12 +334,12 @@ func TestFinalizer_process(t *testing.T) { clients := resources.NewMockResourceClients(t) clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) clients. - On("ForResource", context.Background(), schema.GroupVersionResource{ + On("ForResource", mock.Anything, schema.GroupVersionResource{ Group: "dashboard.grafana.app", Resource: "dashboards", }). @@ -357,7 +364,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(&provisioning.ResourceList{ Items: []provisioning.ResourceListItem{ @@ -381,12 +388,12 @@ func TestFinalizer_process(t *testing.T) { } clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) clients. - On("ForResource", context.Background(), schema.GroupVersionResource{ + On("ForResource", mock.Anything, schema.GroupVersionResource{ Group: "dashboard.grafana.app", Resource: "dashboards", }). @@ -414,7 +421,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(&provisioning.ResourceList{ Items: []provisioning.ResourceListItem{ @@ -438,12 +445,12 @@ func TestFinalizer_process(t *testing.T) { } clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) clients. - On("ForResource", context.Background(), schema.GroupVersionResource{ + On("ForResource", mock.Anything, schema.GroupVersionResource{ Group: "dashboard.grafana.app", Resource: "dashboards", }). @@ -560,3 +567,142 @@ func TestSortResourceListForDeletion(t *testing.T) { }) } } + +func TestFinalizer_processExistingItems_Concurrency(t *testing.T) { + testCases := []struct { + name string + dashboardCount int + folderCount int + maxWorkers int + expectedConcurrency bool + }{ + { + name: "Multiple dashboards processed concurrently", + dashboardCount: 10, + folderCount: 0, + maxWorkers: 5, + expectedConcurrency: true, + }, + { + name: "Single worker processes dashboards sequentially", + dashboardCount: 5, + folderCount: 0, + maxWorkers: 1, + expectedConcurrency: false, + }, + { + name: "Folders processed sequentially regardless of maxWorkers", + dashboardCount: 0, + folderCount: 5, + maxWorkers: 10, + expectedConcurrency: false, + }, + { + name: "Mixed dashboards and folders - dashboards concurrent, folders sequential", + dashboardCount: 10, + folderCount: 3, + maxWorkers: 5, + expectedConcurrency: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Will be used to track concurrent executions + var ( + concurrentCount int64 + maxConcurrent int64 + mu sync.Mutex + ) + + items := provisioning.ResourceList{Items: []provisioning.ResourceListItem{}} + + for i := 0; i < tc.dashboardCount; i++ { + items.Items = append(items.Items, provisioning.ResourceListItem{ + Group: "dashboard.grafana.app", + Resource: "dashboards", + Name: fmt.Sprintf("dashboard-%d", i), + }) + } + + for i := 0; i < tc.folderCount; i++ { + items.Items = append(items.Items, provisioning.ResourceListItem{ + Group: folders.GroupVersion.Group, + Resource: "folders", + Name: fmt.Sprintf("folder-%d", i), + }) + } + + resourceLister := resources.NewMockResourceLister(t) + resourceLister. + On("List", mock.Anything, "default", "my-repo"). + Return(&items, nil) + + clientFactory := resources.NewMockClientFactory(t) + clients := resources.NewMockResourceClients(t) + + client := &mockDynamicClient{ + deleteFunc: func(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error { + // Track concurrent executions + current := atomic.AddInt64(&concurrentCount, 1) + defer atomic.AddInt64(&concurrentCount, -1) + + mu.Lock() + if current > maxConcurrent { + maxConcurrent = current + } + mu.Unlock() + + // Simulate slow client to allow concurrency to build up + time.Sleep(1 * time.Second) + + return nil + }, + } + + clientFactory. + On("Clients", mock.Anything, "default"). + Return(clients, nil) + + clients. + On("ForResource", mock.Anything, mock.Anything). + Return(client, schema.GroupVersionKind{}, nil) + + metrics := registerFinalizerMetrics(prometheus.NewRegistry()) + f := &finalizer{ + lister: resourceLister, + clientFactory: clientFactory, + metrics: &metrics, + maxWorkers: tc.maxWorkers, + } + + repo := &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-repo", + Namespace: "default", + }, + } + + count, err := f.processExistingItems( + context.Background(), + repo, + f.removeResources(context.Background(), logging.DefaultLogger), + ) + + assert.NoError(t, err) + assert.Equal(t, tc.dashboardCount+tc.folderCount, count) + + if tc.expectedConcurrency { + // When concurrent, max concurrent should be > 1 + assert.Greater(t, maxConcurrent, int64(1), + "Expected concurrent execution but maxConcurrent was %d", maxConcurrent) + // Should not exceed maxWorkers + assert.LessOrEqual(t, maxConcurrent, int64(tc.maxWorkers)) + } else { + // When sequential, max concurrent should be 1 + assert.Equal(t, int64(1), maxConcurrent, + "Expected sequential execution but maxConcurrent was %d", maxConcurrent) + } + }) + } +} diff --git a/pkg/registry/apis/provisioning/controller/repository.go b/pkg/registry/apis/provisioning/controller/repository.go index 70e5c3b4a87..681619711e1 100644 --- a/pkg/registry/apis/provisioning/controller/repository.go +++ b/pkg/registry/apis/provisioning/controller/repository.go @@ -84,6 +84,7 @@ func NewRepositoryController( statusPatcher StatusPatcher, registry prometheus.Registerer, tracer tracing.Tracer, + parallelOperations int, ) (*RepositoryController, error) { finalizerMetrics := registerFinalizerMetrics(registry) @@ -104,6 +105,7 @@ func NewRepositoryController( lister: resourceLister, clientFactory: clients, metrics: &finalizerMetrics, + maxWorkers: parallelOperations, }, jobs: jobs, logger: logging.DefaultLogger.With("logger", loggerName), diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index 8797d940408..485f6bfe5d5 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -798,6 +798,7 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH b.statusPatcher, b.registry, b.tracer, + 10, ) if err != nil { return err From 5f2074e84ce339de098e1afa40a8c2487e242e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Jamr=C3=B3z?= Date: Thu, 30 Oct 2025 12:55:06 +0100 Subject: [PATCH 45/72] Explore: Use compact mode only when targeting Tempo (#113037) * Explore: Use compact mode only when targeting Tempo * Fix checking ds type when data source is not in the query object --- public/app/features/explore/Explore.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 5673fdbbbb9..f8bd275930b 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -312,16 +312,20 @@ export class Explore extends PureComponent { */ onSplitOpen = (panelType: string) => { return async (options?: SplitOpenOptions) => { - let compact = true; + let compact = false; /** * Temporary fix grafana-clickhouse-datasource as it requires the query editor to be fully rendered to update the query * Proposed fixes: * - https://github.com/grafana/clickhouse-datasource/issues/1363 - handle query update in data source * - https://github.com/grafana/grafana/issues/110868 - allow data links to provide meta info if the link can be handled in compact mode (default to false) + * Update: + * More data source may struggle with this setting: https://github.com/grafana/grafana/issues/112075 + * We're making it enabled for tempo only and will try to make it optional for other data sources in the future. */ - if (options?.queries?.some((q) => q.datasource?.type === 'grafana-clickhouse-datasource')) { - compact = false; + const dsType = getDataSourceSrv().getInstanceSettings({ uid: options?.datasourceUid })?.type; + if (dsType === 'tempo' || options?.queries?.every((q) => q.datasource?.type === 'tempo')) { + compact = true; } this.props.splitOpen(options ? { ...options, compact } : options); From 80d5cfa1844df4358415c48176970a5e6c325ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ida=20=C5=A0tambuk?= Date: Thu, 30 Oct 2025 13:10:00 +0100 Subject: [PATCH 46/72] CloudWatch: Add tracking for logs anomalies (#113181) - Fix tracking for new logs queries with logGroups field --- .../mocks/dashboardOnLoadedEvent.ts | 30 ++++++++++++++++++- .../datasource/cloudwatch/tracking.test.ts | 5 ++-- .../plugins/datasource/cloudwatch/tracking.ts | 27 ++++++++++++----- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/mocks/dashboardOnLoadedEvent.ts b/public/app/plugins/datasource/cloudwatch/mocks/dashboardOnLoadedEvent.ts index ebe3dcaf1f0..f2095b5c4f2 100644 --- a/public/app/plugins/datasource/cloudwatch/mocks/dashboardOnLoadedEvent.ts +++ b/public/app/plugins/datasource/cloudwatch/mocks/dashboardOnLoadedEvent.ts @@ -2,7 +2,7 @@ import { DashboardLoadedEvent } from '@grafana/data'; import { CloudWatchQuery } from '../types'; -const baseLogsQuery = { +const baseLegacyLogsQuery = { datasource: { type: 'cloudwatch', uid: 'P7DC3E4760CFAC4AP', @@ -17,6 +17,15 @@ const baseLogsQuery = { statsGroups: [], }; +const baseLogsQuery = { + ...baseLegacyLogsQuery, + logGroups: [ + { arn: 'arn:test', name: 'log-group-1' }, + { arn: 'arn:test2', name: 'log-group-2' }, + ], + logGroupNames: undefined, +}; + export const CloudWatchDashboardLoadedEvent = new DashboardLoadedEvent({ dashboardId: 'dashboard123', orgId: 1, @@ -474,6 +483,9 @@ export const CloudWatchDashboardLoadedEvent = new DashboardLoadedEvent({ sqlExpression: '', statistic: 'Average', }, + { + ...baseLegacyLogsQuery, + }, { ...baseLogsQuery, }, @@ -779,6 +791,22 @@ export const CloudWatchDashboardLoadedEvent = new DashboardLoadedEvent({ sqlExpression: '', statistic: '', }, + { + refId: 'A', + region: 'default', + queryMode: 'Logs', + logsMode: 'Anomalies', + anomalyDetectionARN: '', + suppressionState: 'suppressed', + }, + { + refId: 'A', + region: 'default', + queryMode: 'Logs', + logsMode: 'Anomalies', + anomalyDetectionARN: '', + suppressionState: 'all', + }, ] as CloudWatchQuery[], }, }); diff --git a/public/app/plugins/datasource/cloudwatch/tracking.test.ts b/public/app/plugins/datasource/cloudwatch/tracking.test.ts index 822cf07a5d1..f66f0a2ad25 100644 --- a/public/app/plugins/datasource/cloudwatch/tracking.test.ts +++ b/public/app/plugins/datasource/cloudwatch/tracking.test.ts @@ -27,10 +27,11 @@ describe('onDashboardLoadedHandler', () => { dashboard_id: 'dashboard123', grafana_version: 'v9.0.0', org_id: 1, - logs_queries_count: 5, - logs_cwli_queries_count: 2, + logs_queries_count: 8, + logs_cwli_queries_count: 3, logs_sql_queries_count: 1, logs_ppl_queries_count: 2, + log_anomalies_queries_count: 2, metrics_queries_count: 21, metrics_query_builder_count: 3, metrics_query_code_count: 4, diff --git a/public/app/plugins/datasource/cloudwatch/tracking.ts b/public/app/plugins/datasource/cloudwatch/tracking.ts index 4bf91191f1e..51a4559ee1e 100644 --- a/public/app/plugins/datasource/cloudwatch/tracking.ts +++ b/public/app/plugins/datasource/cloudwatch/tracking.ts @@ -1,13 +1,15 @@ import { DashboardLoadedEvent } from '@grafana/data'; import { config, reportInteraction } from '@grafana/runtime'; -import { isCloudWatchLogsQuery, isCloudWatchMetricsQuery } from './guards'; +import { isCloudWatchLogsQuery, isCloudWatchMetricsQuery, isLogsAnomaliesQuery } from './guards'; import { migrateMetricQuery } from './migrations/metricQueryMigrations'; import pluginJson from './plugin.json'; import { + CloudWatchLogsAnomaliesQuery, CloudWatchLogsQuery, CloudWatchMetricsQuery, CloudWatchQuery, + LogsMode, LogsQueryLanguage, MetricEditorMode, MetricQueryType, @@ -31,6 +33,9 @@ type CloudWatchOnDashboardLoadedTrackingEvent = { /* The number of Logs queries that use PPL language */ logs_ppl_queries_count: number; + /* The number of log anomalies queries */ + log_anomalies_queries_count: number; + /* The number of CloudWatch metrics queries present in the dashboard*/ metrics_queries_count: number; @@ -77,7 +82,8 @@ export const onDashboardLoadedHandler = ({ return; } - let logsQueries: CloudWatchLogsQuery[] = []; + let logsInsightsQueries: CloudWatchLogsQuery[] = []; + let logAnomaliesQueries: CloudWatchLogsAnomaliesQuery[] = []; let metricsQueries: CloudWatchMetricsQuery[] = []; for (const query of cloudWatchQueries) { @@ -85,8 +91,12 @@ export const onDashboardLoadedHandler = ({ continue; } - if (isCloudWatchLogsQuery(query)) { - query.logGroupNames?.length && logsQueries.push(query); + const isLogsInsightsQuery = + isCloudWatchLogsQuery(query) && (!query.logsMode || query.logsMode === LogsMode.Insights); + if (isLogsInsightsQuery) { + (query.logGroupNames?.length || query.logGroups?.length) && logsInsightsQueries.push(query); + } else if (isLogsAnomaliesQuery(query)) { + logAnomaliesQueries.push(query); } else if (isCloudWatchMetricsQuery(query)) { const migratedQuery = migrateMetricQuery(query); filterMetricsQuery(migratedQuery) && metricsQueries.push(query); @@ -97,12 +107,13 @@ export const onDashboardLoadedHandler = ({ grafana_version: grafanaVersion, dashboard_id: dashboardId, org_id: orgId, - logs_queries_count: logsQueries?.length, - logs_cwli_queries_count: logsQueries?.filter( + logs_queries_count: logsInsightsQueries?.length + logAnomaliesQueries.length, + logs_cwli_queries_count: logsInsightsQueries?.filter( (q) => !q.queryLanguage || q.queryLanguage === LogsQueryLanguage.CWLI ).length, - logs_sql_queries_count: logsQueries?.filter((q) => q.queryLanguage === LogsQueryLanguage.SQL).length, - logs_ppl_queries_count: logsQueries?.filter((q) => q.queryLanguage === LogsQueryLanguage.PPL).length, + logs_sql_queries_count: logsInsightsQueries?.filter((q) => q.queryLanguage === LogsQueryLanguage.SQL).length, + logs_ppl_queries_count: logsInsightsQueries?.filter((q) => q.queryLanguage === LogsQueryLanguage.PPL).length, + log_anomalies_queries_count: logAnomaliesQueries.length, metrics_queries_count: metricsQueries?.length, metrics_search_count: 0, metrics_search_builder_count: 0, From f185377c68bb2f0a943e9974034ba00d04d6511f Mon Sep 17 00:00:00 2001 From: Ezequiel Victorero Date: Thu, 30 Oct 2025 09:29:40 -0300 Subject: [PATCH 47/72] ShortURL: Use RTK api for creation (#113185) --- public/app/core/utils/shortLinks.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/core/utils/shortLinks.ts b/public/app/core/utils/shortLinks.ts index c01988d2262..011b5c66baa 100644 --- a/public/app/core/utils/shortLinks.ts +++ b/public/app/core/utils/shortLinks.ts @@ -1,10 +1,10 @@ import memoizeOne from 'memoize-one'; -import { generatedAPI } from '@grafana/api-clients/rtkq/shorturl/v1alpha1'; import { AbsoluteTimeRange, LogRowModel, UrlQueryMap } from '@grafana/data'; import { t } from '@grafana/i18n'; import { getBackendSrv, config, locationService } from '@grafana/runtime'; import { sceneGraph, SceneTimeRangeLike, VizPanel } from '@grafana/scenes'; +import { shortURLAPIv1alpha1 } from 'app/api/clients/shorturl/v1alpha1'; import { notifyApp } from 'app/core/actions'; import { createErrorNotification, createSuccessNotification } from 'app/core/copy/appNotification'; import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; @@ -46,7 +46,7 @@ export const createShortLink = async function (path: string) { if (config.featureToggles.useKubernetesShortURLsAPI) { // Use RTK API - it handles caching/failures/retries automatically const result = await dispatch( - generatedAPI.endpoints.createShortUrl.initiate({ + shortURLAPIv1alpha1.endpoints.createShortUrl.initiate({ shortUrl: { apiVersion: 'shorturl.grafana.app/v1alpha1', kind: 'ShortURL', From d303746ff90563669cfbeae7f6d1041770c7bec6 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 30 Oct 2025 15:57:03 +0300 Subject: [PATCH 48/72] ShortURL: Use UpdateStatus client (#111170) --- apps/shorturl/pkg/app/app.go | 14 ++- pkg/api/short_url_test.go | 9 +- pkg/registry/apps/shorturl/conversions.go | 9 +- pkg/registry/apps/shorturl/legacy_storage.go | 52 +------- pkg/registry/apps/shorturl/register.go | 16 +++ pkg/registry/apps/shorturl/status.go | 111 ++++++++++++++++++ .../apiserver/appinstaller/installer.go | 10 +- pkg/services/apiserver/appinstaller/server.go | 52 +++++--- pkg/services/shorturls/shorturl.go | 6 +- .../shorturls/shorturlimpl/shorturl.go | 10 +- pkg/services/shorturls/shorturlimpl/store.go | 8 +- pkg/tests/apis/shorturl/shorturl_test.go | 83 ++++++------- 12 files changed, 250 insertions(+), 130 deletions(-) create mode 100644 pkg/registry/apps/shorturl/status.go diff --git a/apps/shorturl/pkg/app/app.go b/apps/shorturl/pkg/app/app.go index 4c85ef97435..bb97db78a93 100644 --- a/apps/shorturl/pkg/app/app.go +++ b/apps/shorturl/pkg/app/app.go @@ -30,11 +30,12 @@ var ( func New(cfg app.Config) (app.App, error) { cfg.KubeConfig.APIPath = "apis" - client, err := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()). + tmp, err := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()). ClientFor(shorturlv1alpha1.ShortURLKind()) if err != nil { return nil, fmt.Errorf("unable to create client") } + client := shorturlv1alpha1.NewShortURLClient(tmp) simpleConfig := simple.AppConfig{ Name: "shorturl", @@ -81,8 +82,8 @@ func New(cfg app.Config) (app.App, error) { Name: req.ResourceIdentifier.Name, } - info := &shorturlv1alpha1.ShortURL{} - if err := client.GetInto(ctx, id, info); err != nil { + info, err := client.Get(ctx, id) + if err != nil { return err } @@ -93,7 +94,12 @@ func New(cfg app.Config) (app.App, error) { if err != nil { logging.FromContext(ctx).Warn("unable to create background identity", "err", err) } else { - _, _ = client.Update(ctx, id, info, resource.UpdateOptions{}) + _, err = client.UpdateStatus(ctx, id, info.Status, resource.UpdateOptions{ + ResourceVersion: info.ResourceVersion, + }) + if err != nil { + logging.FromContext(ctx).Warn("unable to update status", "err", err) + } } }() diff --git a/pkg/api/short_url_test.go b/pkg/api/short_url_test.go index 6d3da5fbe0c..116d4f70328 100644 --- a/pkg/api/short_url_test.go +++ b/pkg/api/short_url_test.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/shorturls" @@ -31,7 +32,7 @@ func TestShortURLAPIEndpoint(t *testing.T) { Path: cmd.Path, } service := &fakeShortURLService{ - createShortURLFunc: func(ctx context.Context, user *user.SignedInUser, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { + createShortURLFunc: func(ctx context.Context, user identity.Requester, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { return createResp, nil }, createConvertShortURLToDTO: func(shortURL *shorturls.ShortUrl, appURL string) *dtos.ShortURL { @@ -81,7 +82,7 @@ func createShortURLScenario(t *testing.T, desc string, url string, routePattern } type fakeShortURLService struct { - createShortURLFunc func(ctx context.Context, user *user.SignedInUser, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) + createShortURLFunc func(ctx context.Context, user identity.Requester, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) createConvertShortURLToDTO func(shortURL *shorturls.ShortUrl, appURL string) *dtos.ShortURL } @@ -89,11 +90,11 @@ func (s *fakeShortURLService) List(ctx context.Context, orgID int64) ([]*shortur return nil, nil } -func (s *fakeShortURLService) GetShortURLByUID(ctx context.Context, user *user.SignedInUser, uid string) (*shorturls.ShortUrl, error) { +func (s *fakeShortURLService) GetShortURLByUID(ctx context.Context, user identity.Requester, uid string) (*shorturls.ShortUrl, error) { return nil, nil } -func (s *fakeShortURLService) CreateShortURL(ctx context.Context, user *user.SignedInUser, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { +func (s *fakeShortURLService) CreateShortURL(ctx context.Context, user identity.Requester, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { if s.createShortURLFunc != nil { return s.createShortURLFunc(ctx, user, cmd) } diff --git a/pkg/registry/apps/shorturl/conversions.go b/pkg/registry/apps/shorturl/conversions.go index 979106d3bda..c23f014adbd 100644 --- a/pkg/registry/apps/shorturl/conversions.go +++ b/pkg/registry/apps/shorturl/conversions.go @@ -21,10 +21,17 @@ func convertToK8sResource(v *shorturls.ShortUrl, namespacer request.NamespaceMap status := shorturl.ShortURLStatus{ LastSeenAt: v.LastSeenAt, } + + // resourceVersion can't be 0, since we are using the lastSeenAt value, when it's zero we default to current time + resourceVersion := fmt.Sprintf("%d", v.LastSeenAt) + if v.LastSeenAt == 0 { + resourceVersion = fmt.Sprintf("%d", time.Now().UnixMilli()) + } + p := &shorturl.ShortURL{ ObjectMeta: metav1.ObjectMeta{ Name: v.Uid, - ResourceVersion: fmt.Sprintf("%d", v.LastSeenAt), + ResourceVersion: resourceVersion, CreationTimestamp: metav1.NewTime(time.UnixMilli(v.CreatedAt)), Namespace: namespacer(v.OrgId), }, diff --git a/pkg/registry/apps/shorturl/legacy_storage.go b/pkg/registry/apps/shorturl/legacy_storage.go index c9462359236..de36560b679 100644 --- a/pkg/registry/apps/shorturl/legacy_storage.go +++ b/pkg/registry/apps/shorturl/legacy_storage.go @@ -16,9 +16,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/shorturls" - "github.com/grafana/grafana/pkg/services/user" ) var ( @@ -87,13 +85,7 @@ func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.Ge return nil, err } - // Convert any identity.Requester to *user.SignedInUser - signedInUser, err := convertRequesterToSignedInUser(requester) - if err != nil { - return nil, fmt.Errorf("failed to convert requester: %w", err) - } - - dto, err := s.service.GetShortURLByUID(ctx, signedInUser, name) + dto, err := s.service.GetShortURLByUID(ctx, requester, name) if err != nil || dto == nil { if errors.Is(err, shorturls.ErrShortURLNotFound) || err == nil { err = k8serrors.NewNotFound(shorturl.ShortURLKind().GroupVersionResource().GroupResource(), name) @@ -114,12 +106,6 @@ func (s *legacyStorage) Create(ctx context.Context, return nil, err } - // Convert any identity.Requester to *user.SignedInUser - signedInUser, err := convertRequesterToSignedInUser(requester) - if err != nil { - return nil, fmt.Errorf("failed to convert requester: %w", err) - } - if createValidation != nil { if err := createValidation(ctx, obj.DeepCopyObject()); err != nil { return nil, err @@ -133,7 +119,7 @@ func (s *legacyStorage) Create(ctx context.Context, Path: p.Spec.Path, UID: p.Name, } - out, err := s.service.CreateShortURL(ctx, signedInUser, cmd) + out, err := s.service.CreateShortURL(ctx, requester, cmd) if err != nil { return nil, err } @@ -154,13 +140,7 @@ func (s *legacyStorage) Update(ctx context.Context, return nil, false, err } - // Convert any identity.Requester to *user.SignedInUser - signedInUser, err := convertRequesterToSignedInUser(requester) - if err != nil { - return nil, false, fmt.Errorf("failed to convert requester: %w", err) - } - - shortURL, err := s.service.GetShortURLByUID(ctx, signedInUser, name) + shortURL, err := s.service.GetShortURLByUID(ctx, requester, name) if err != nil || shortURL == nil { if errors.Is(err, shorturls.ErrShortURLNotFound) || err == nil { err = k8serrors.NewNotFound(shorturl.ShortURLKind().GroupVersionResource().GroupResource(), name) @@ -173,7 +153,7 @@ func (s *legacyStorage) Update(ctx context.Context, return nil, false, err } // Fetch the updated short URL to return - updatedLegacyShortURL, err := s.service.GetShortURLByUID(ctx, signedInUser, name) + updatedLegacyShortURL, err := s.service.GetShortURLByUID(ctx, requester, name) if err != nil { return nil, false, err } @@ -199,27 +179,3 @@ func (s *legacyStorage) Delete(ctx context.Context, name string, deleteValidatio func (s *legacyStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) { return nil, fmt.Errorf("DeleteCollection for shorturl not implemented") } - -// convertRequesterToSignedInUser converts any identity.Requester to *user.SignedInUser -// This is needed because some legacy shorturls service methods still expect SignedInUser -func convertRequesterToSignedInUser(requester identity.Requester) (*user.SignedInUser, error) { - // If it's already a SignedInUser, return it directly - if signedInUser, ok := requester.(*user.SignedInUser); ok { - return signedInUser, nil - } - - // If it's a StaticRequester (service identity), convert it - if staticRequester, ok := requester.(*identity.StaticRequester); ok { - return &user.SignedInUser{ - UserID: staticRequester.UserID, // Used for CreatedBy field - OrgID: staticRequester.OrgID, // Used in SQL queries - }, nil - } - - // If it's an authn.Identity, use its SignedInUser method - if authnIdentity, ok := requester.(*authn.Identity); ok { - return authnIdentity.SignedInUser(), nil - } - - return nil, fmt.Errorf("unsupported identity type") -} diff --git a/pkg/registry/apps/shorturl/register.go b/pkg/registry/apps/shorturl/register.go index a44593ceabe..423eae92073 100644 --- a/pkg/registry/apps/shorturl/register.go +++ b/pkg/registry/apps/shorturl/register.go @@ -5,6 +5,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/rest" restclient "k8s.io/client-go/rest" "github.com/grafana/grafana-app-sdk/app" @@ -86,3 +87,18 @@ func (s *ShortURLAppInstaller) GetLegacyStorage(requested schema.GroupVersionRes ) return legacyStore } + +func (s *ShortURLAppInstaller) GetLegacyStatus(requested schema.GroupVersionResource, unified *appsdkapiserver.StatusREST) rest.Storage { + gvr := shorturl.ShortURLKind().GroupVersionResource() + if requested.String() != gvr.String() { + return nil + } + return &statusDualWriter{ + gv: gvr.GroupVersion(), + status: unified, + legacy: &legacyStorage{ + service: s.service, + namespacer: s.namespacer, + }, + } +} diff --git a/pkg/registry/apps/shorturl/status.go b/pkg/registry/apps/shorturl/status.go new file mode 100644 index 00000000000..ed9fe063a98 --- /dev/null +++ b/pkg/registry/apps/shorturl/status.go @@ -0,0 +1,111 @@ +package shorturl + +import ( + "context" + "errors" + "fmt" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/rest" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + + "github.com/grafana/grafana-app-sdk/k8s/apiserver" + "github.com/grafana/grafana-app-sdk/logging" + shorturl "github.com/grafana/grafana/apps/shorturl/pkg/apis/shorturl/v1alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/shorturls" +) + +type statusDualWriter struct { + gv schema.GroupVersion + status *apiserver.StatusREST + legacy *legacyStorage +} + +var ( + _ rest.Patcher = (*statusDualWriter)(nil) + _ rest.Storage = (*statusDualWriter)(nil) + _ rest.ResetFieldsStrategy = (*statusDualWriter)(nil) +) + +// Destroy implements rest.Storage. +func (s *statusDualWriter) Destroy() {} + +// New implements rest.Storage. +func (s *statusDualWriter) New() runtime.Object { + return s.legacy.New() +} + +// Get implements rest.Patcher. +func (s *statusDualWriter) Get(ctx context.Context, name string, options *v1.GetOptions) (runtime.Object, error) { + return s.legacy.Get(ctx, name, options) +} + +// Update implements rest.Patcher. +func (s *statusDualWriter) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *v1.UpdateOptions) (runtime.Object, bool, error) { + requester, err := identity.GetRequester(ctx) + if err != nil { + return nil, false, err + } + + shortURL, err := s.legacy.service.GetShortURLByUID(ctx, requester, name) + if err != nil || shortURL == nil { + if errors.Is(err, shorturls.ErrShortURLNotFound) || err == nil { + err = k8serrors.NewNotFound(shorturl.ShortURLKind().GroupVersionResource().GroupResource(), name) + } + return nil, false, err + } + + // This ignores the incoming and updates it directly + err = s.legacy.service.UpdateLastSeenAt(ctx, shortURL) + if err != nil { + return nil, false, err + } + + getter := func(getter rest.Getter) (*shorturl.ShortURL, error) { + obj, err := getter.Get(ctx, name, &v1.GetOptions{}) + if err != nil { + return nil, err + } + val, ok := obj.(*shorturl.ShortURL) + if !ok { + return nil, fmt.Errorf("expected ShortURL but got %T", obj) + } + return val, nil + } + + legacy, err := getter(s.legacy) + if err != nil { + return nil, false, err // unable to get legacy object + } + + unified, err := getter(s.status) + if err != nil { + logging.FromContext(ctx).Warn("unable to read unified status", "error", err) + return legacy, false, nil + } + + // Use the same status from legacy in unified + unified.Status = legacy.Status + + _, _, err = s.status.Update(ctx, name, rest.DefaultUpdatedObjectInfo(unified), createValidation, updateValidation, false, options) + if err != nil { + logging.FromContext(ctx).Warn("error updating unified status", "error", err) + } + + return legacy, false, err +} + +// GetResetFields implements rest.ResetFieldsStrategy +func (s *statusDualWriter) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + fields := map[fieldpath.APIVersion]*fieldpath.Set{ + fieldpath.APIVersion(s.gv.String()): fieldpath.NewSet( + fieldpath.MakePathOrDie("spec"), + fieldpath.MakePathOrDie("metadata"), + ), + } + return fields +} diff --git a/pkg/services/apiserver/appinstaller/installer.go b/pkg/services/apiserver/appinstaller/installer.go index a89d2b84f9a..d7742eedf12 100644 --- a/pkg/services/apiserver/appinstaller/installer.go +++ b/pkg/services/apiserver/appinstaller/installer.go @@ -12,18 +12,18 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstore "k8s.io/apiserver/pkg/server/storage" "k8s.io/kube-openapi/pkg/common" appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" "github.com/grafana/grafana-app-sdk/logging" - "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" - grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" grafanaapiserveroptions "github.com/grafana/grafana/pkg/services/apiserver/options" + "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" ) type LegacyStorageGetterFunc func(schema.GroupVersionResource) grafanarest.Storage @@ -32,6 +32,12 @@ type LegacyStorageProvider interface { GetLegacyStorage(schema.GroupVersionResource) grafanarest.Storage } +// In the rare case that that legacy needs to support the status subresource +// Unlike resource storage, dual writing must be managed explicitly +type LegacyStatusProvider interface { + GetLegacyStatus(schema.GroupVersionResource, *appsdkapiserver.StatusREST) rest.Storage +} + type AuthorizerProvider interface { GetAuthorizer() authorizer.Authorizer } diff --git a/pkg/services/apiserver/appinstaller/server.go b/pkg/services/apiserver/appinstaller/server.go index 2e5d46b2b3d..ab688a46fb9 100644 --- a/pkg/services/apiserver/appinstaller/server.go +++ b/pkg/services/apiserver/appinstaller/server.go @@ -60,25 +60,41 @@ func (s *serverWrapper) InstallAPIGroup(apiGroupInfo *genericapiserver.APIGroupI continue } storage := s.configureStorage(gr, dualWriteSupported, restStorage) - if unifiedStorage, ok := storage.(grafanarest.Storage); ok && dualWriteSupported { - log.Debug("Configuring dual writer for storage", "resource", gr.String(), "version", v, "storagePath", storagePath) - dw, err := NewDualWriter( - s.ctx, - gr, - s.storageOpts, - legacyProvider.GetLegacyStorage(gr.WithVersion(v)), - unifiedStorage, - s.kvStore, - s.lock, - s.namespaceMapper, - s.dualWriteService, - s.dualWriterMetrics, - s.builderMetrics, - ) - if err != nil { - return err + if dualWriteSupported { + if unifiedStorage, ok := storage.(grafanarest.Storage); ok { + log.Debug("Configuring dual writer for storage", "resource", gr.String(), "version", v, "storagePath", storagePath) + storage, err = NewDualWriter( + s.ctx, + gr, + s.storageOpts, + legacyProvider.GetLegacyStorage(gr.WithVersion(v)), + unifiedStorage, + s.kvStore, + s.lock, + s.namespaceMapper, + s.dualWriteService, + s.dualWriterMetrics, + s.builderMetrics, + ) + if err != nil { + return err + } + } else if statusRest, ok := storage.(*appsdkapiserver.StatusREST); ok { + parentPath := strings.TrimSuffix(storagePath, "/status") + parentStore, ok := apiGroupInfo.VersionedResourcesStorageMap[v][parentPath] + if ok { + if _, isMode4or5 := parentStore.(*genericregistry.Store); !isMode4or5 { + // When legacy resources have status, the dual writing must be handled explicitly + if statusProvider, ok := s.installer.(LegacyStatusProvider); ok { + storage = statusProvider.GetLegacyStatus(gr.WithVersion(v), statusRest) + } else { + log.Warn("skipped registering status sub-resource that does not support dual writing", + "resource", gr.String(), "version", v, "storagePath", storagePath) + continue + } + } + } } - storage = dw } apiGroupInfo.VersionedResourcesStorageMap[v][storagePath] = storage } diff --git a/pkg/services/shorturls/shorturl.go b/pkg/services/shorturls/shorturl.go index d62f469f85c..99040011f50 100644 --- a/pkg/services/shorturls/shorturl.go +++ b/pkg/services/shorturls/shorturl.go @@ -4,12 +4,12 @@ import ( "context" "github.com/grafana/grafana/pkg/api/dtos" - "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/apimachinery/identity" ) type Service interface { - GetShortURLByUID(ctx context.Context, user *user.SignedInUser, uid string) (*ShortUrl, error) - CreateShortURL(ctx context.Context, user *user.SignedInUser, cmd *dtos.CreateShortURLCmd) (*ShortUrl, error) + GetShortURLByUID(ctx context.Context, user identity.Requester, uid string) (*ShortUrl, error) + CreateShortURL(ctx context.Context, user identity.Requester, cmd *dtos.CreateShortURLCmd) (*ShortUrl, error) UpdateLastSeenAt(ctx context.Context, shortURL *ShortUrl) error DeleteStaleShortURLs(ctx context.Context, cmd *DeleteShortUrlCommand) error ConvertShortURLToDTO(shortURL *ShortUrl, appURL string) *dtos.ShortURL diff --git a/pkg/services/shorturls/shorturlimpl/shorturl.go b/pkg/services/shorturls/shorturlimpl/shorturl.go index 51753b74c6e..a363a4d7627 100644 --- a/pkg/services/shorturls/shorturlimpl/shorturl.go +++ b/pkg/services/shorturls/shorturlimpl/shorturl.go @@ -8,9 +8,9 @@ import ( "time" "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/shorturls" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" ) @@ -28,7 +28,7 @@ func ProvideService(db db.DB) *ShortURLService { } } -func (s ShortURLService) GetShortURLByUID(ctx context.Context, user *user.SignedInUser, uid string) (*shorturls.ShortUrl, error) { +func (s ShortURLService) GetShortURLByUID(ctx context.Context, user identity.Requester, uid string) (*shorturls.ShortUrl, error) { return s.SQLStore.Get(ctx, user, uid) } @@ -40,7 +40,7 @@ func (s ShortURLService) List(ctx context.Context, orgID int64) ([]*shorturls.Sh return s.SQLStore.List(ctx, orgID) } -func (s ShortURLService) CreateShortURL(ctx context.Context, user *user.SignedInUser, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { +func (s ShortURLService) CreateShortURL(ctx context.Context, user identity.Requester, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { relPath := strings.TrimSpace(cmd.Path) if path.IsAbs(relPath) { @@ -74,12 +74,12 @@ func (s ShortURLService) CreateShortURL(ctx context.Context, user *user.SignedIn now := time.Now().Unix() shortURL := shorturls.ShortUrl{ - OrgId: user.OrgID, + OrgId: user.GetOrgID(), Uid: uid, Path: relPath, - CreatedBy: user.UserID, CreatedAt: now, } + shortURL.CreatedBy, _ = user.GetInternalID() if err := s.SQLStore.Insert(ctx, &shortURL); err != nil { return nil, shorturls.ErrShortURLInternal.Errorf("failed to insert shorturl: %w", err) diff --git a/pkg/services/shorturls/shorturlimpl/store.go b/pkg/services/shorturls/shorturlimpl/store.go index 6e0a072d23c..ad74b269d45 100644 --- a/pkg/services/shorturls/shorturlimpl/store.go +++ b/pkg/services/shorturls/shorturlimpl/store.go @@ -3,13 +3,13 @@ package shorturlimpl import ( "context" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/shorturls" - "github.com/grafana/grafana/pkg/services/user" ) type store interface { - Get(ctx context.Context, user *user.SignedInUser, uid string) (*shorturls.ShortUrl, error) + Get(ctx context.Context, user identity.Requester, uid string) (*shorturls.ShortUrl, error) Update(ctx context.Context, shortURL *shorturls.ShortUrl) error Insert(ctx context.Context, shortURL *shorturls.ShortUrl) error Delete(ctx context.Context, cmd *shorturls.DeleteShortUrlCommand) error @@ -20,10 +20,10 @@ type sqlStore struct { db db.DB } -func (s sqlStore) Get(ctx context.Context, user *user.SignedInUser, uid string) (*shorturls.ShortUrl, error) { +func (s sqlStore) Get(ctx context.Context, user identity.Requester, uid string) (*shorturls.ShortUrl, error) { var shortURL shorturls.ShortUrl err := s.db.WithDbSession(ctx, func(dbSession *db.Session) error { - exists, err := dbSession.Where("org_id=? AND uid=?", user.OrgID, uid).Get(&shortURL) + exists, err := dbSession.Where("org_id=? AND uid=?", user.GetOrgID(), uid).Get(&shortURL) if err != nil { return err } diff --git a/pkg/tests/apis/shorturl/shorturl_test.go b/pkg/tests/apis/shorturl/shorturl_test.go index 86445846acf..1b6f44dd7e4 100644 --- a/pkg/tests/apis/shorturl/shorturl_test.go +++ b/pkg/tests/apis/shorturl/shorturl_test.go @@ -5,13 +5,14 @@ import ( "fmt" "net/http" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" + shorturlV1 "github.com/grafana/grafana/apps/shorturl/pkg/apis/shorturl/v1alpha1" "github.com/grafana/grafana/pkg/api/dtos" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/apiserver/options" @@ -28,11 +29,7 @@ func TestMain(m *testing.M) { testsuite.Run(m) } -var gvr = schema.GroupVersionResource{ - Group: "shorturl.grafana.app", - Version: "v1alpha1", - Resource: "shorturls", -} +var gvr = shorturlV1.ShortURLKind().GroupVersionResource() var RESOURCEGROUP = gvr.GroupResource().String() @@ -78,29 +75,31 @@ func TestIntegrationShortURL(t *testing.T) { doLegacyOnlyTests(t, helper) }) - for _, mode := range []grafanarest.DualWriterMode{ - grafanarest.Mode1, - grafanarest.Mode2, - // grafanarest.Mode3, TODO: the /goto function needs to use an UpdateStatus client - // grafanarest.Mode4, - } { - t.Run(fmt.Sprintf("with dual write (unified storage, mode %d)", mode), func(t *testing.T) { - helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeUnified, - EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesShortURLs, - }, - UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ - RESOURCEGROUP: { - DualWriterMode: mode, + t.Run("modes", func(t *testing.T) { + for _, mode := range []grafanarest.DualWriterMode{ + grafanarest.Mode1, + grafanarest.Mode2, + grafanarest.Mode3, + grafanarest.Mode4, + } { + t.Run(fmt.Sprintf("dual write (unified storage, mode %d)", mode), func(t *testing.T) { + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: false, + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeUnified, + EnableFeatureToggles: []string{ + featuremgmt.FlagKubernetesShortURLs, }, - }, + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + RESOURCEGROUP: { + DualWriterMode: mode, + }, + }, + }) + doDualWriteTests(t, helper, mode) }) - doDualWriteTests(t, helper, mode) - }) - } + } + }) t.Run("with dual write (unified storage, mode 5)", func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ @@ -173,7 +172,7 @@ func doLegacyOnlyTests(t *testing.T, helper *apis.K8sTestHelper) { User: client.Args.User, Method: http.MethodGet, Path: "/goto/" + uid + "?orgId=default", - }, (*interface{})(nil)) + }, (*any)(nil)) assert.Equal(t, 302, redirectResponse.Response.StatusCode) }) } @@ -278,21 +277,23 @@ func doDualWriteTests(t *testing.T, helper *apis.K8sTestHelper, mode grafanarest User: client.Args.User, Method: http.MethodGet, Path: "/goto/" + uid + "?orgId=default", - }, (*interface{})(nil)) + }, (*any)(nil)) assert.Equal(t, 302, redirectResponse.Response.StatusCode) - // Verify lastSeenAt was updated (should be > 0 now) - found, err := client.Resource.Get(context.Background(), uid, metav1.GetOptions{}) - require.NoError(t, err) - status, exists := found.Object["status"].(map[string]interface{}) - assert.True(t, exists) - lastSeenAt, exists := status["lastSeenAt"].(int64) - assert.True(t, exists) + require.EventuallyWithT(t, func(t *assert.CollectT) { + // Verify lastSeenAt was updated (should be > 0 now) + found, err := client.Resource.Get(context.Background(), uid, metav1.GetOptions{}) + require.NoError(t, err) - assert.Greater(t, lastSeenAt, int64(0)) + lastSeenAt, exists, err := unstructured.NestedInt64(found.Object, "status", "lastSeenAt") + require.NoError(t, err) + require.True(t, exists) + + require.Greater(t, lastSeenAt, int64(1), "lastSeenAt should be greater than 1 after redirect") + }, time.Second*5, time.Millisecond*75, "lastSeenAt should be updated after redirect") // Clean up - err = client.Resource.Delete(context.Background(), uid, metav1.DeleteOptions{}) + err := client.Resource.Delete(context.Background(), uid, metav1.DeleteOptions{}) require.NoError(t, err) }) } @@ -458,7 +459,7 @@ func doUnifiedOnlyTests(t *testing.T, helper *apis.K8sTestHelper) { User: client.Args.User, Method: http.MethodGet, Path: "/goto/" + uid + "?orgId=default", - }, (*interface{})(nil)) + }, (*any)(nil)) assert.Equal(t, 302, redirectResponse.Response.StatusCode) // Clean up @@ -505,9 +506,9 @@ func getFromBothAPIs(t *testing.T, if legacyShortURL != nil { // If legacy API returns data, verify consistency - spec, ok := k8sResource.Object["spec"].(map[string]interface{}) + spec, ok := k8sResource.Object["spec"].(map[string]any) require.True(t, ok) - status, ok := k8sResource.Object["status"].(map[string]interface{}) + status, ok := k8sResource.Object["status"].(map[string]any) require.True(t, ok) assert.Equal(t, legacyShortURL.Uid, k8sResource.GetName()) assert.Equal(t, legacyShortURL.Path, spec["path"].(string)) From 05dc9b2be1cf5a9dfd06564403175b6417f18d50 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Thu, 30 Oct 2025 13:08:14 +0000 Subject: [PATCH 49/72] API Clients: Add lazy hooks to clients (#113226) --- .../rtkq/advisor/v0alpha1/endpoints.gen.ts | 7 + .../correlations/v0alpha1/endpoints.gen.ts | 3 + .../rtkq/dashboard/v0alpha1/endpoints.gen.ts | 8 + .../rtkq/folder/v1beta1/endpoints.gen.ts | 7 + .../rtkq/iam/v0alpha1/endpoints.gen.ts | 15 ++ .../src/clients/rtkq/legacy/endpoints.gen.ts | 143 ++++++++++++++++++ .../rtkq/migrate-to-cloud/endpoints.gen.ts | 8 + .../rtkq/playlist/v0alpha1/endpoints.gen.ts | 4 + .../rtkq/preferences/user/endpoints.gen.ts | 8 +- .../preferences/v1alpha1/endpoints.gen.ts | 6 + .../provisioning/v0alpha1/endpoints.gen.ts | 18 +++ .../rtkq/shorturl/v1alpha1/endpoints.gen.ts | 5 + .../src/scripts/generate-rtk-apis.ts | 16 +- 13 files changed, 241 insertions(+), 7 deletions(-) diff --git a/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts index 8cf2fec2b60..4b4de61be87 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts @@ -1008,24 +1008,31 @@ export type CheckTypeList = { }; export const { useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, useListCheckQuery, + useLazyListCheckQuery, useCreateCheckMutation, useDeletecollectionCheckMutation, useGetCheckQuery, + useLazyGetCheckQuery, useReplaceCheckMutation, useDeleteCheckMutation, useUpdateCheckMutation, useGetCheckStatusQuery, + useLazyGetCheckStatusQuery, useReplaceCheckStatusMutation, useUpdateCheckStatusMutation, useListCheckTypeQuery, + useLazyListCheckTypeQuery, useCreateCheckTypeMutation, useDeletecollectionCheckTypeMutation, useGetCheckTypeQuery, + useLazyGetCheckTypeQuery, useReplaceCheckTypeMutation, useDeleteCheckTypeMutation, useUpdateCheckTypeMutation, useGetCheckTypeStatusQuery, + useLazyGetCheckTypeStatusQuery, useReplaceCheckTypeStatusMutation, useUpdateCheckTypeStatusMutation, } = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts index 9f40ec322a5..6cfab61cdba 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts @@ -511,10 +511,13 @@ export type Status = { export type Patch = object; export const { useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, useListCorrelationQuery, + useLazyListCorrelationQuery, useCreateCorrelationMutation, useDeletecollectionCorrelationMutation, useGetCorrelationQuery, + useLazyGetCorrelationQuery, useReplaceCorrelationMutation, useDeleteCorrelationMutation, useUpdateCorrelationMutation, diff --git a/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts index 9e4aee6b516..6ca3505d067 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts @@ -1061,21 +1061,29 @@ export type SearchResults = { }; export const { useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, useListDashboardQuery, + useLazyListDashboardQuery, useCreateDashboardMutation, useDeletecollectionDashboardMutation, useGetDashboardQuery, + useLazyGetDashboardQuery, useReplaceDashboardMutation, useDeleteDashboardMutation, useUpdateDashboardMutation, useGetDashboardDtoQuery, + useLazyGetDashboardDtoQuery, useListLibraryPanelQuery, + useLazyListLibraryPanelQuery, useCreateLibraryPanelMutation, useDeletecollectionLibraryPanelMutation, useGetLibraryPanelQuery, + useLazyGetLibraryPanelQuery, useReplaceLibraryPanelMutation, useDeleteLibraryPanelMutation, useUpdateLibraryPanelMutation, useGetSearchQuery, + useLazyGetSearchQuery, useGetSearchSortableQuery, + useLazyGetSearchSortableQuery, } = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts index b618f8b335a..9ef9e4ebfab 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts @@ -562,15 +562,22 @@ export type FolderInfoList = { }; export const { useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, useListFolderQuery, + useLazyListFolderQuery, useCreateFolderMutation, useDeletecollectionFolderMutation, useGetFolderQuery, + useLazyGetFolderQuery, useReplaceFolderMutation, useDeleteFolderMutation, useUpdateFolderMutation, useGetFolderAccessQuery, + useLazyGetFolderAccessQuery, useGetFolderChildrenQuery, + useLazyGetFolderChildrenQuery, useGetFolderCountsQuery, + useLazyGetFolderCountsQuery, useGetFolderParentsQuery, + useLazyGetFolderParentsQuery, } = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts index bd1585fc928..3645b4b3373 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts @@ -1733,41 +1733,56 @@ export type UserTeamList = { }; export const { useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, useGetDisplayMappingQuery, + useLazyGetDisplayMappingQuery, useListServiceAccountQuery, + useLazyListServiceAccountQuery, useCreateServiceAccountMutation, useDeletecollectionServiceAccountMutation, useGetServiceAccountQuery, + useLazyGetServiceAccountQuery, useReplaceServiceAccountMutation, useDeleteServiceAccountMutation, useUpdateServiceAccountMutation, useGetServiceAccountTokensQuery, + useLazyGetServiceAccountTokensQuery, useListSsoSettingQuery, + useLazyListSsoSettingQuery, useGetSsoSettingQuery, + useLazyGetSsoSettingQuery, useReplaceSsoSettingMutation, useDeleteSsoSettingMutation, useUpdateSsoSettingMutation, useListTeamBindingQuery, + useLazyListTeamBindingQuery, useCreateTeamBindingMutation, useDeletecollectionTeamBindingMutation, useGetTeamBindingQuery, + useLazyGetTeamBindingQuery, useReplaceTeamBindingMutation, useDeleteTeamBindingMutation, useUpdateTeamBindingMutation, useListTeamQuery, + useLazyListTeamQuery, useCreateTeamMutation, useDeletecollectionTeamMutation, useGetTeamQuery, + useLazyGetTeamQuery, useReplaceTeamMutation, useDeleteTeamMutation, useUpdateTeamMutation, useGetTeamMembersQuery, + useLazyGetTeamMembersQuery, useListUserQuery, + useLazyListUserQuery, useCreateUserMutation, useDeletecollectionUserMutation, useGetUserQuery, + useLazyGetUserQuery, useReplaceUserMutation, useDeleteUserMutation, useUpdateUserMutation, useGetUserTeamsQuery, + useLazyGetUserTeamsQuery, } = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts index eb201201379..bd6ff32f5bf 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts @@ -6502,319 +6502,462 @@ export type NotificationTemplateContent = { export const { useSearchResultMutation, useListRolesQuery, + useLazyListRolesQuery, useCreateRoleMutation, useDeleteRoleMutation, useGetRoleQuery, + useLazyGetRoleQuery, useUpdateRoleMutation, useGetRoleAssignmentsQuery, + useLazyGetRoleAssignmentsQuery, useSetRoleAssignmentsMutation, useGetAccessControlStatusQuery, + useLazyGetAccessControlStatusQuery, useListTeamsRolesMutation, useListTeamRolesQuery, + useLazyListTeamRolesQuery, useAddTeamRoleMutation, useSetTeamRolesMutation, useRemoveTeamRoleMutation, useListUsersRolesMutation, useListUserRolesQuery, + useLazyListUserRolesQuery, useAddUserRoleMutation, useSetUserRolesMutation, useRemoveUserRoleMutation, useGetResourceDescriptionQuery, + useLazyGetResourceDescriptionQuery, useGetResourcePermissionsQuery, + useLazyGetResourcePermissionsQuery, useSetResourcePermissionsMutation, useSetResourcePermissionsForBuiltInRoleMutation, useSetResourcePermissionsForTeamMutation, useSetResourcePermissionsForUserMutation, useGetSyncStatusQuery, + useLazyGetSyncStatusQuery, useReloadLdapCfgMutation, useGetLdapStatusQuery, + useLazyGetLdapStatusQuery, usePostSyncUserWithLdapMutation, useGetUserFromLdapQuery, + useLazyGetUserFromLdapQuery, useAdminProvisioningReloadAccessControlMutation, useAdminProvisioningReloadDashboardsMutation, useAdminProvisioningReloadDatasourcesMutation, useAdminProvisioningReloadPluginsMutation, useAdminGetSettingsQuery, + useLazyAdminGetSettingsQuery, useAdminGetStatsQuery, + useLazyAdminGetStatsQuery, useAdminCreateUserMutation, useAdminDeleteUserMutation, useAdminGetUserAuthTokensQuery, + useLazyAdminGetUserAuthTokensQuery, useAdminDisableUserMutation, useAdminEnableUserMutation, useAdminLogoutUserMutation, useAdminUpdateUserPasswordMutation, useAdminUpdateUserPermissionsMutation, useGetUserQuotaQuery, + useLazyGetUserQuotaQuery, useUpdateUserQuotaMutation, useAdminRevokeUserAuthTokenMutation, useGetAnnotationsQuery, + useLazyGetAnnotationsQuery, usePostAnnotationMutation, usePostGraphiteAnnotationMutation, useMassDeleteAnnotationsMutation, useGetAnnotationTagsQuery, + useLazyGetAnnotationTagsQuery, useDeleteAnnotationByIdMutation, useGetAnnotationByIdQuery, + useLazyGetAnnotationByIdQuery, usePatchAnnotationMutation, useUpdateAnnotationMutation, useListDevicesQuery, + useLazyListDevicesQuery, useSearchDevicesQuery, + useLazySearchDevicesQuery, useGetSessionListQuery, + useLazyGetSessionListQuery, useCreateSessionMutation, useDeleteSessionMutation, useGetSessionQuery, + useLazyGetSessionQuery, useCreateSnapshotMutation, useGetSnapshotQuery, + useLazyGetSnapshotQuery, useCancelSnapshotMutation, useUploadSnapshotMutation, useGetShapshotListQuery, + useLazyGetShapshotListQuery, useGetResourceDependenciesQuery, + useLazyGetResourceDependenciesQuery, useGetCloudMigrationTokenQuery, + useLazyGetCloudMigrationTokenQuery, useCreateCloudMigrationTokenMutation, useDeleteCloudMigrationTokenMutation, useRouteConvertPrometheusCortexGetRulesQuery, + useLazyRouteConvertPrometheusCortexGetRulesQuery, useRouteConvertPrometheusCortexPostRuleGroupsMutation, useRouteConvertPrometheusCortexDeleteNamespaceMutation, useRouteConvertPrometheusCortexGetNamespaceQuery, + useLazyRouteConvertPrometheusCortexGetNamespaceQuery, useRouteConvertPrometheusCortexPostRuleGroupMutation, useRouteConvertPrometheusCortexDeleteRuleGroupMutation, useRouteConvertPrometheusCortexGetRuleGroupQuery, + useLazyRouteConvertPrometheusCortexGetRuleGroupQuery, useRouteConvertPrometheusGetRulesQuery, + useLazyRouteConvertPrometheusGetRulesQuery, useRouteConvertPrometheusPostRuleGroupsMutation, useRouteConvertPrometheusDeleteNamespaceMutation, useRouteConvertPrometheusGetNamespaceQuery, + useLazyRouteConvertPrometheusGetNamespaceQuery, useRouteConvertPrometheusPostRuleGroupMutation, useRouteConvertPrometheusDeleteRuleGroupMutation, useRouteConvertPrometheusGetRuleGroupQuery, + useLazyRouteConvertPrometheusGetRuleGroupQuery, useSearchDashboardSnapshotsQuery, + useLazySearchDashboardSnapshotsQuery, useCalculateDashboardDiffMutation, usePostDashboardMutation, useGetHomeDashboardQuery, + useLazyGetHomeDashboardQuery, useImportDashboardMutation, useInterpolateDashboardMutation, useListPublicDashboardsQuery, + useLazyListPublicDashboardsQuery, useGetDashboardTagsQuery, + useLazyGetDashboardTagsQuery, useGetPublicDashboardQuery, + useLazyGetPublicDashboardQuery, useCreatePublicDashboardMutation, useDeletePublicDashboardMutation, useUpdatePublicDashboardMutation, useDeleteDashboardByUidMutation, useGetDashboardByUidQuery, + useLazyGetDashboardByUidQuery, useGetDashboardPermissionsListByUidQuery, + useLazyGetDashboardPermissionsListByUidQuery, useUpdateDashboardPermissionsByUidMutation, useRestoreDashboardVersionByUidMutation, useGetDashboardVersionsByUidQuery, + useLazyGetDashboardVersionsByUidQuery, useGetDashboardVersionByUidQuery, + useLazyGetDashboardVersionByUidQuery, useGetDataSourcesQuery, + useLazyGetDataSourcesQuery, useAddDataSourceMutation, useGetCorrelationsQuery, + useLazyGetCorrelationsQuery, useGetDataSourceIdByNameQuery, + useLazyGetDataSourceIdByNameQuery, useDeleteDataSourceByNameMutation, useGetDataSourceByNameQuery, + useLazyGetDataSourceByNameQuery, useDatasourceProxyDeleteByUiDcallsMutation, useDatasourceProxyGetByUiDcallsQuery, + useLazyDatasourceProxyGetByUiDcallsQuery, useDatasourceProxyPostByUiDcallsMutation, useGetCorrelationsBySourceUidQuery, + useLazyGetCorrelationsBySourceUidQuery, useCreateCorrelationMutation, useGetCorrelationQuery, + useLazyGetCorrelationQuery, useUpdateCorrelationMutation, useDeleteDataSourceByUidMutation, useGetDataSourceByUidQuery, + useLazyGetDataSourceByUidQuery, useUpdateDataSourceByUidMutation, useDeleteCorrelationMutation, useCheckDatasourceHealthWithUidQuery, + useLazyCheckDatasourceHealthWithUidQuery, useGetTeamLbacRulesApiQuery, + useLazyGetTeamLbacRulesApiQuery, useUpdateTeamLbacRulesApiMutation, useCallDatasourceResourceWithUidQuery, + useLazyCallDatasourceResourceWithUidQuery, useGetDataSourceCacheConfigQuery, + useLazyGetDataSourceCacheConfigQuery, useSetDataSourceCacheConfigMutation, useCleanDataSourceCacheMutation, useDisableDataSourceCacheMutation, useEnableDataSourceCacheMutation, useQueryMetricsWithExpressionsMutation, useGetFoldersQuery, + useLazyGetFoldersQuery, useCreateFolderMutation, useDeleteFolderMutation, useGetFolderByUidQuery, + useLazyGetFolderByUidQuery, useUpdateFolderMutation, useGetFolderDescendantCountsQuery, + useLazyGetFolderDescendantCountsQuery, useMoveFolderMutation, useGetFolderPermissionListQuery, + useLazyGetFolderPermissionListQuery, useUpdateFolderPermissionsMutation, useGetMappedGroupsQuery, + useLazyGetMappedGroupsQuery, useDeleteGroupMappingsMutation, useCreateGroupMappingsMutation, useUpdateGroupMappingsMutation, useGetGroupRolesQuery, + useLazyGetGroupRolesQuery, useGetHealthQuery, + useLazyGetHealthQuery, useGetLibraryElementsQuery, + useLazyGetLibraryElementsQuery, useCreateLibraryElementMutation, useGetLibraryElementByNameQuery, + useLazyGetLibraryElementByNameQuery, useDeleteLibraryElementByUidMutation, useGetLibraryElementByUidQuery, + useLazyGetLibraryElementByUidQuery, useUpdateLibraryElementMutation, useGetLibraryElementConnectionsQuery, + useLazyGetLibraryElementConnectionsQuery, useGetStatusQuery, + useLazyGetStatusQuery, useRefreshLicenseStatsQuery, + useLazyRefreshLicenseStatsQuery, useDeleteLicenseTokenMutation, useGetLicenseTokenQuery, + useLazyGetLicenseTokenQuery, usePostLicenseTokenMutation, usePostRenewLicenseTokenMutation, useGetSamlLogoutQuery, + useLazyGetSamlLogoutQuery, useGetCurrentOrgQuery, + useLazyGetCurrentOrgQuery, useUpdateCurrentOrgMutation, useUpdateCurrentOrgAddressMutation, useGetPendingOrgInvitesQuery, + useLazyGetPendingOrgInvitesQuery, useAddOrgInviteMutation, useRevokeInviteMutation, useGetOrgPreferencesQuery, + useLazyGetOrgPreferencesQuery, usePatchOrgPreferencesMutation, useUpdateOrgPreferencesMutation, useGetCurrentOrgQuotaQuery, + useLazyGetCurrentOrgQuotaQuery, useGetOrgUsersForCurrentOrgQuery, + useLazyGetOrgUsersForCurrentOrgQuery, useAddOrgUserToCurrentOrgMutation, useGetOrgUsersForCurrentOrgLookupQuery, + useLazyGetOrgUsersForCurrentOrgLookupQuery, useRemoveOrgUserForCurrentOrgMutation, useUpdateOrgUserForCurrentOrgMutation, useSearchOrgsQuery, + useLazySearchOrgsQuery, useCreateOrgMutation, useGetOrgByNameQuery, + useLazyGetOrgByNameQuery, useDeleteOrgByIdMutation, useGetOrgByIdQuery, + useLazyGetOrgByIdQuery, useUpdateOrgMutation, useUpdateOrgAddressMutation, useGetOrgQuotaQuery, + useLazyGetOrgQuotaQuery, useUpdateOrgQuotaMutation, useGetOrgUsersQuery, + useLazyGetOrgUsersQuery, useAddOrgUserMutation, useSearchOrgUsersQuery, + useLazySearchOrgUsersQuery, useRemoveOrgUserMutation, useUpdateOrgUserMutation, useSearchPlaylistsQuery, + useLazySearchPlaylistsQuery, useCreatePlaylistMutation, useDeletePlaylistMutation, useGetPlaylistQuery, + useLazyGetPlaylistQuery, useUpdatePlaylistMutation, useGetPlaylistItemsQuery, + useLazyGetPlaylistItemsQuery, useViewPublicDashboardQuery, + useLazyViewPublicDashboardQuery, useGetPublicAnnotationsQuery, + useLazyGetPublicAnnotationsQuery, useQueryPublicDashboardMutation, useSearchQueriesQuery, + useLazySearchQueriesQuery, useCreateQueryMutation, useUnstarQueryMutation, useStarQueryMutation, useDeleteQueryMutation, usePatchQueryCommentMutation, useListRecordingRulesQuery, + useLazyListRecordingRulesQuery, useCreateRecordingRuleMutation, useUpdateRecordingRuleMutation, useTestCreateRecordingRuleMutation, useDeleteRecordingRuleWriteTargetMutation, useGetRecordingRuleWriteTargetQuery, + useLazyGetRecordingRuleWriteTargetQuery, useCreateRecordingRuleWriteTargetMutation, useDeleteRecordingRuleMutation, useGetReportsQuery, + useLazyGetReportsQuery, useCreateReportMutation, useGetReportsByDashboardUidQuery, + useLazyGetReportsByDashboardUidQuery, useSendReportMutation, useGetSettingsImageQuery, + useLazyGetSettingsImageQuery, useRenderReportCsVsQuery, + useLazyRenderReportCsVsQuery, useRenderReportPdFsQuery, + useLazyRenderReportPdFsQuery, useGetReportSettingsQuery, + useLazyGetReportSettingsQuery, useSaveReportSettingsMutation, useSendTestEmailMutation, usePostAcsMutation, useGetMetadataQuery, + useLazyGetMetadataQuery, useGetSloQuery, + useLazyGetSloQuery, usePostSloMutation, useSearchQuery, + useLazySearchQuery, useListSortOptionsQuery, + useLazyListSortOptionsQuery, useCreateServiceAccountMutation, useSearchOrgServiceAccountsWithPagingQuery, + useLazySearchOrgServiceAccountsWithPagingQuery, useDeleteServiceAccountMutation, useRetrieveServiceAccountQuery, + useLazyRetrieveServiceAccountQuery, useUpdateServiceAccountMutation, useListTokensQuery, + useLazyListTokensQuery, useCreateTokenMutation, useDeleteTokenMutation, useRetrieveJwksQuery, + useLazyRetrieveJwksQuery, useGetSharingOptionsQuery, + useLazyGetSharingOptionsQuery, useCreateDashboardSnapshotMutation, useDeleteDashboardSnapshotByDeleteKeyQuery, + useLazyDeleteDashboardSnapshotByDeleteKeyQuery, useDeleteDashboardSnapshotMutation, useGetDashboardSnapshotQuery, + useLazyGetDashboardSnapshotQuery, useCreateTeamMutation, useSearchTeamsQuery, + useLazySearchTeamsQuery, useRemoveTeamGroupApiQueryMutation, useGetTeamGroupsApiQuery, + useLazyGetTeamGroupsApiQuery, useAddTeamGroupApiMutation, useSearchTeamGroupsQuery, + useLazySearchTeamGroupsQuery, useDeleteTeamByIdMutation, useGetTeamByIdQuery, + useLazyGetTeamByIdQuery, useUpdateTeamMutation, useGetTeamMembersQuery, + useLazyGetTeamMembersQuery, useAddTeamMemberMutation, useSetTeamMembershipsMutation, useRemoveTeamMemberMutation, useUpdateTeamMemberMutation, useGetTeamPreferencesQuery, + useLazyGetTeamPreferencesQuery, useUpdateTeamPreferencesMutation, useGetSignedInUserQuery, + useLazyGetSignedInUserQuery, useUpdateSignedInUserMutation, useGetUserAuthTokensQuery, + useLazyGetUserAuthTokensQuery, useUpdateUserEmailQuery, + useLazyUpdateUserEmailQuery, useClearHelpFlagsQuery, + useLazyClearHelpFlagsQuery, useSetHelpFlagMutation, useGetSignedInUserOrgListQuery, + useLazyGetSignedInUserOrgListQuery, useChangeUserPasswordMutation, useGetUserPreferencesQuery, + useLazyGetUserPreferencesQuery, usePatchUserPreferencesMutation, useUpdateUserPreferencesMutation, useGetUserQuotasQuery, + useLazyGetUserQuotasQuery, useRevokeUserAuthTokenMutation, useUnstarDashboardByUidMutation, useStarDashboardByUidMutation, useGetSignedInUserTeamListQuery, + useLazyGetSignedInUserTeamListQuery, useUserSetUsingOrgMutation, useSearchUsersQuery, + useLazySearchUsersQuery, useGetUserByLoginOrEmailQuery, + useLazyGetUserByLoginOrEmailQuery, useSearchUsersWithPagingQuery, + useLazySearchUsersWithPagingQuery, useGetUserByIdQuery, + useLazyGetUserByIdQuery, useUpdateUserMutation, useGetUserOrgListQuery, + useLazyGetUserOrgListQuery, useGetUserTeamsQuery, + useLazyGetUserTeamsQuery, useRouteGetAlertRulesQuery, + useLazyRouteGetAlertRulesQuery, useRoutePostAlertRuleMutation, useRouteGetAlertRulesExportQuery, + useLazyRouteGetAlertRulesExportQuery, useRouteDeleteAlertRuleMutation, useRouteGetAlertRuleQuery, + useLazyRouteGetAlertRuleQuery, useRoutePutAlertRuleMutation, useRouteGetAlertRuleExportQuery, + useLazyRouteGetAlertRuleExportQuery, useRouteGetContactpointsQuery, + useLazyRouteGetContactpointsQuery, useRoutePostContactpointsMutation, useRouteGetContactpointsExportQuery, + useLazyRouteGetContactpointsExportQuery, useRouteDeleteContactpointsMutation, useRoutePutContactpointMutation, useRouteDeleteAlertRuleGroupMutation, useRouteGetAlertRuleGroupQuery, + useLazyRouteGetAlertRuleGroupQuery, useRoutePutAlertRuleGroupMutation, useRouteGetAlertRuleGroupExportQuery, + useLazyRouteGetAlertRuleGroupExportQuery, useRouteGetMuteTimingsQuery, + useLazyRouteGetMuteTimingsQuery, useRoutePostMuteTimingMutation, useRouteExportMuteTimingsQuery, + useLazyRouteExportMuteTimingsQuery, useRouteDeleteMuteTimingMutation, useRouteGetMuteTimingQuery, + useLazyRouteGetMuteTimingQuery, useRoutePutMuteTimingMutation, useRouteExportMuteTimingQuery, + useLazyRouteExportMuteTimingQuery, useRouteResetPolicyTreeMutation, useRouteGetPolicyTreeQuery, + useLazyRouteGetPolicyTreeQuery, useRoutePutPolicyTreeMutation, useRouteGetPolicyTreeExportQuery, + useLazyRouteGetPolicyTreeExportQuery, useRouteGetTemplatesQuery, + useLazyRouteGetTemplatesQuery, useRouteDeleteTemplateMutation, useRouteGetTemplateQuery, + useLazyRouteGetTemplateQuery, useRoutePutTemplateMutation, useListAllProvidersSettingsQuery, + useLazyListAllProvidersSettingsQuery, useRemoveProviderSettingsMutation, useGetProviderSettingsQuery, + useLazyGetProviderSettingsQuery, useUpdateProviderSettingsMutation, } = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/migrate-to-cloud/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/migrate-to-cloud/endpoints.gen.ts index 87bcb349ecd..d1aff7226ee 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/migrate-to-cloud/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/migrate-to-cloud/endpoints.gen.ts @@ -405,18 +405,26 @@ export type LibraryElementResponseIsAResponseStructForLibraryElementDto = { }; export const { useGetSessionListQuery, + useLazyGetSessionListQuery, useCreateSessionMutation, useDeleteSessionMutation, useGetSessionQuery, + useLazyGetSessionQuery, useCreateSnapshotMutation, useGetSnapshotQuery, + useLazyGetSnapshotQuery, useCancelSnapshotMutation, useUploadSnapshotMutation, useGetShapshotListQuery, + useLazyGetShapshotListQuery, useGetResourceDependenciesQuery, + useLazyGetResourceDependenciesQuery, useGetCloudMigrationTokenQuery, + useLazyGetCloudMigrationTokenQuery, useCreateCloudMigrationTokenMutation, useDeleteCloudMigrationTokenMutation, useGetDashboardByUidQuery, + useLazyGetDashboardByUidQuery, useGetLibraryElementByUidQuery, + useLazyGetLibraryElementByUidQuery, } = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts index 51bf82c23c6..8e1bdfc1c4c 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts @@ -598,14 +598,18 @@ export type Status = { export type Patch = object; export const { useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, useListPlaylistQuery, + useLazyListPlaylistQuery, useCreatePlaylistMutation, useDeletecollectionPlaylistMutation, useGetPlaylistQuery, + useLazyGetPlaylistQuery, useReplacePlaylistMutation, useDeletePlaylistMutation, useUpdatePlaylistMutation, useGetPlaylistStatusQuery, + useLazyGetPlaylistStatusQuery, useReplacePlaylistStatusMutation, useUpdatePlaylistStatusMutation, } = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/preferences/user/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/preferences/user/endpoints.gen.ts index 60162b33858..71107ce1072 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/preferences/user/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/preferences/user/endpoints.gen.ts @@ -102,5 +102,9 @@ export type UpdatePrefsCmd = { timezone?: 'utc' | 'browser'; weekStart?: string; }; -export const { useGetUserPreferencesQuery, usePatchUserPreferencesMutation, useUpdateUserPreferencesMutation } = - injectedRtkApi; +export const { + useGetUserPreferencesQuery, + useLazyGetUserPreferencesQuery, + usePatchUserPreferencesMutation, + useUpdateUserPreferencesMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts index 2ea0fd00730..0edda5148ea 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts @@ -778,17 +778,23 @@ export type StarsList = { }; export const { useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, useListPreferencesQuery, + useLazyListPreferencesQuery, useCreatePreferencesMutation, useMergedPreferencesQuery, + useLazyMergedPreferencesQuery, useGetPreferencesQuery, + useLazyGetPreferencesQuery, useReplacePreferencesMutation, useDeletePreferencesMutation, useUpdatePreferencesMutation, useListStarsQuery, + useLazyListStarsQuery, useCreateStarsMutation, useDeletecollectionStarsMutation, useGetStarsQuery, + useLazyGetStarsQuery, useReplaceStarsMutation, useDeleteStarsMutation, useUpdateStarsMutation, diff --git a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts index 7d566227d1a..a3c5d30952a 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts @@ -1639,39 +1639,57 @@ export type ResourceStats = { }; export const { useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, useListJobQuery, + useLazyListJobQuery, useCreateJobMutation, useDeletecollectionJobMutation, useGetJobQuery, + useLazyGetJobQuery, useReplaceJobMutation, useDeleteJobMutation, useUpdateJobMutation, useListRepositoryQuery, + useLazyListRepositoryQuery, useCreateRepositoryMutation, useDeletecollectionRepositoryMutation, useGetRepositoryQuery, + useLazyGetRepositoryQuery, useReplaceRepositoryMutation, useDeleteRepositoryMutation, useUpdateRepositoryMutation, useGetRepositoryFilesQuery, + useLazyGetRepositoryFilesQuery, useGetRepositoryFilesWithPathQuery, + useLazyGetRepositoryFilesWithPathQuery, useReplaceRepositoryFilesWithPathMutation, useCreateRepositoryFilesWithPathMutation, useDeleteRepositoryFilesWithPathMutation, useGetRepositoryHistoryQuery, + useLazyGetRepositoryHistoryQuery, useGetRepositoryHistoryWithPathQuery, + useLazyGetRepositoryHistoryWithPathQuery, useGetRepositoryJobsQuery, + useLazyGetRepositoryJobsQuery, useCreateRepositoryJobsMutation, useGetRepositoryJobsWithPathQuery, + useLazyGetRepositoryJobsWithPathQuery, useGetRepositoryRefsQuery, + useLazyGetRepositoryRefsQuery, useGetRepositoryRenderWithPathQuery, + useLazyGetRepositoryRenderWithPathQuery, useGetRepositoryResourcesQuery, + useLazyGetRepositoryResourcesQuery, useGetRepositoryStatusQuery, + useLazyGetRepositoryStatusQuery, useReplaceRepositoryStatusMutation, useUpdateRepositoryStatusMutation, useCreateRepositoryTestMutation, useGetRepositoryWebhookQuery, + useLazyGetRepositoryWebhookQuery, useCreateRepositoryWebhookMutation, useGetFrontendSettingsQuery, + useLazyGetFrontendSettingsQuery, useGetResourceStatsQuery, + useLazyGetResourceStatsQuery, } = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts index 374c96482a4..a4bfc1df1ba 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts @@ -599,15 +599,20 @@ export type GetGoto = { }; export const { useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, useListShortUrlQuery, + useLazyListShortUrlQuery, useCreateShortUrlMutation, useDeletecollectionShortUrlMutation, useGetShortUrlQuery, + useLazyGetShortUrlQuery, useReplaceShortUrlMutation, useDeleteShortUrlMutation, useUpdateShortUrlMutation, useGetShortUrlGotoQuery, + useLazyGetShortUrlGotoQuery, useGetShortUrlStatusQuery, + useLazyGetShortUrlStatusQuery, useReplaceShortUrlStatusMutation, useUpdateShortUrlStatusMutation, } = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts b/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts index 18fd3cbee59..a9422a02815 100644 --- a/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts +++ b/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts @@ -17,6 +17,12 @@ type OperationDefinition = { }; type EndpointMatcher = string[] | ((operationName: string, operationDefinition: OperationDefinition) => boolean); +const defaultHooksOptions = { + queries: true, + lazyQueries: true, + mutations: true, +}; + /** * Helper to return consistent base API generation config */ @@ -28,7 +34,7 @@ const createAPIConfig = (app: string, version: string, filterEndpoints?: Endpoin apiFile: `../clients/rtkq/${app}/${version}/baseAPI.ts`, filterEndpoints, tag: true, - hooks: true, + hooks: defaultHooksOptions, ...additional, }, }; @@ -43,7 +49,7 @@ const config: ConfigFile = { // OpenAPI3 client with all endpoints '../clients/rtkq/legacy/endpoints.gen.ts': { schemaFile: path.join(basePath, 'public/openapi3.json'), - hooks: true, + hooks: defaultHooksOptions, tag: true, apiFile: '../clients/rtkq/legacy/baseAPI.ts', filterEndpoints: (_name, operation) => !operation.operation.deprecated, @@ -51,7 +57,7 @@ const config: ConfigFile = { '../clients/rtkq/migrate-to-cloud/endpoints.gen.ts': { schemaFile: path.join(basePath, 'public/openapi3.json'), apiFile: '../clients/rtkq/migrate-to-cloud/baseAPI.ts', - hooks: true, + hooks: defaultHooksOptions, filterEndpoints: [ 'getSessionList', 'getSession', @@ -76,13 +82,13 @@ const config: ConfigFile = { }, '../clients/rtkq/preferences/user/endpoints.gen.ts': { schemaFile: path.join(basePath, 'public/openapi3.json'), - hooks: true, + hooks: defaultHooksOptions, apiFile: '../clients/rtkq/preferences/user/baseAPI.ts', filterEndpoints: ['getUserPreferences', 'updateUserPreferences', 'patchUserPreferences'], }, '../clients/rtkq/user/endpoints.gen.ts': { schemaFile: path.join(basePath, 'public/openapi3.json'), - hooks: true, + hooks: defaultHooksOptions, apiFile: '../clients/rtkq/user/baseAPI.ts', filterEndpoints: ['starDashboardByUid', 'unstarDashboardByUid'], }, From c4879522795b77d141386c6faecb91df4e1d11b5 Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Thu, 30 Oct 2025 10:04:08 -0400 Subject: [PATCH 50/72] Table: Support DataLinks and Actions in SparklineCell (#112244) * Table: Support DataLinks in SparklineCell * add data links to sparkline gdev * fix migrator test * Clean up single action use case --- .../panel-table/table_sparkline_cell.v42.json | 24 +++++++++++++++++-- .../panel-table/table_sparkline_cell.json | 22 ++++++++++++++++- .../Table/TableNG/Cells/SparklineCell.tsx | 23 ++++++++++++------ .../src/components/Table/TableNG/TableNG.tsx | 8 +------ .../TableNG/components/MaybeWrapWithLink.tsx | 1 + .../src/components/Table/TableNG/types.ts | 1 - .../src/components/Table/TableNG/utils.ts | 9 ------- 7 files changed, 61 insertions(+), 27 deletions(-) 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 3c8310630d7..dae0d524c23 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 @@ -208,7 +208,27 @@ "value": 80 } ] - } + }, + "actions": [ + { + "fetch": { + "body": "{}", + "headers": [["Content-Type", "application/json"]], + "method": "GET", + "queryParams": [], + "url": "/api/health" + }, + "title": "Get instance health", + "type": "fetch" + } + ], + "links": [ + { + "targetBlank": true, + "title": "Google Grafana", + "url": "https://google.com/search?q=grafana" + } + ] }, "overrides": [] }, @@ -592,4 +612,4 @@ "title": "Panel Tests - Table - Sparklines", "uid": "d6373b49-1957-4f00-9218-ee2120d3ecd9", "weekStart": "" -} \ No newline at end of file +} diff --git a/devenv/dev-dashboards/panel-table/table_sparkline_cell.json b/devenv/dev-dashboards/panel-table/table_sparkline_cell.json index 3337c794279..4725580cf0e 100644 --- a/devenv/dev-dashboards/panel-table/table_sparkline_cell.json +++ b/devenv/dev-dashboards/panel-table/table_sparkline_cell.json @@ -204,7 +204,27 @@ "value": 80 } ] - } + }, + "actions": [ + { + "fetch": { + "body": "{}", + "headers": [["Content-Type", "application/json"]], + "method": "GET", + "queryParams": [], + "url": "/api/health" + }, + "title": "Get instance health", + "type": "fetch" + } + ], + "links": [ + { + "targetBlank": true, + "title": "Google Grafana", + "url": "https://google.com/search?q=grafana" + } + ] }, "overrides": [] }, diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx index acedb2091a7..453fdb237b0 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx @@ -17,6 +17,7 @@ import { import { measureText } from '../../../../utils/measureText'; import { FormattedValueDisplay } from '../../../FormattedValueDisplay/FormattedValueDisplay'; import { Sparkline } from '../../../Sparkline/Sparkline'; +import { MaybeWrapWithLink } from '../components/MaybeWrapWithLink'; import { SparklineCellProps, TableCellStyles } from '../types'; import { getAlignmentFactor, getCellOptions, prepareSparklineValue } from '../utils'; @@ -38,7 +39,11 @@ export const SparklineCell = (props: SparklineCellProps) => { const sparkline = prepareSparklineValue(value, field); if (!sparkline) { - return <>{field.config.noValue || t('grafana-ui.table.sparkline.no-data', 'no data')}; + return ( + + {field.config.noValue || t('grafana-ui.table.sparkline.no-data', 'no data')} + + ); } // Get the step from the first two values to null-fill the x-axis based on timerange @@ -87,10 +92,10 @@ export const SparklineCell = (props: SparklineCellProps) => { } return ( - <> + {valueElement} - + ); }; @@ -107,8 +112,12 @@ function getTableSparklineCellOptions(field: Field): TableSparklineCellOptions { export const getStyles: TableCellStyles = (theme, { textAlign }) => css({ - width: '100%', - gap: theme.spacing(1), - justifyContent: 'space-between', - ...(textAlign === 'right' && { flexDirection: 'row-reverse' }), + '&, & > a': { + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: theme.spacing(1), + ...(textAlign === 'right' && { flexDirection: 'row-reverse' }), + }, }); diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index 5f64c58773a..a3772785d63 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -94,7 +94,6 @@ import { predicateByName, shouldTextOverflow, shouldTextWrap, - withDataLinksActionsTooltip, getSummaryCellTextAlign, parseStyleJson, IS_SAFARI_26, @@ -406,7 +405,6 @@ export function TableNG(props: TableNGProps) { const result: FromFieldsResult = { columns: [], cellRootRenderers: {}, - colsWithTooltip: {}, }; let lastRowIdx = -1; @@ -464,7 +462,6 @@ export function TableNG(props: TableNGProps) { const shouldOverflow = !IS_SAFARI_26 && rowHeight !== 'auto' && (shouldTextOverflow(field) || Boolean(maxRowHeight)); const textWrap = rowHeight === 'auto' || shouldTextWrap(field); - const withTooltip = withDataLinksActionsTooltip(field, cellType); const canBeColorized = canFieldBeColorized(cellType, applyToRowBgFn); const cellStyleOptions: TableCellStyleOptions = { textAlign, @@ -473,8 +470,6 @@ export function TableNG(props: TableNGProps) { maxHeight: maxRowHeight, }; - result.colsWithTooltip[displayName] = withTooltip; - const defaultCellStyles = getDefaultCellStyles(theme, cellStyleOptions); const cellSpecificStyles = getCellSpecificStyles(cellType, field, theme, cellStyleOptions); const linkStyles = getLinkStyles(theme, canBeColorized); @@ -737,7 +732,7 @@ export function TableNG(props: TableNGProps) { ); const [nestedFieldWidths] = useColWidths(firstRowNestedData?.fields ?? [], availableWidth); - const { columns, cellRootRenderers, colsWithTooltip } = useMemo(() => { + const { columns, cellRootRenderers } = useMemo(() => { const result = fromFields(visibleFields, widths); // if nested frames are present, augment the columns to include the nested table expander column. @@ -805,7 +800,6 @@ export function TableNG(props: TableNGProps) { const field = columns[column.idx].field; if ( - colsWithTooltip[getDisplayName(field)] && target instanceof HTMLElement && // this walks up the tree to find either a faux link wrapper or the cell root // it then only proceeds if we matched the faux link wrapper diff --git a/packages/grafana-ui/src/components/Table/TableNG/components/MaybeWrapWithLink.tsx b/packages/grafana-ui/src/components/Table/TableNG/components/MaybeWrapWithLink.tsx index b43728dd1e8..7758bf8fd20 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/components/MaybeWrapWithLink.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/components/MaybeWrapWithLink.tsx @@ -34,4 +34,5 @@ export const MaybeWrapWithLink = memo(({ field, rowIdx, children }: MaybeWrapWit // raw value return children; }); + MaybeWrapWithLink.displayName = 'MaybeWrapWithLink'; diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts index c4d63b2826b..b828dfddbb6 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/types.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts @@ -308,7 +308,6 @@ export type CellRootRenderer = (key: React.Key, props: CellRendererProps; - colsWithTooltip: Record; } export interface FooterFieldState extends FieldState { diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts index e3ab23b3e0e..867ee96b983 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts @@ -955,15 +955,6 @@ export function getApplyToRowBgFn( } } -/** @internal */ -export function withDataLinksActionsTooltip(field: Field, cellType: TableCellDisplayMode) { - return ( - cellType !== TableCellDisplayMode.DataLinks && - cellType !== TableCellDisplayMode.Actions && - (field.config.links?.length ?? 0) + (field.config.actions?.length ?? 0) > 1 - ); -} - /** @internal */ export function canFieldBeColorized( cellType: TableCellDisplayMode, From c3d7dbc2585f84a65dba3b8c54490e10fff63102 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Thu, 30 Oct 2025 10:05:12 -0400 Subject: [PATCH 51/72] SQL Expressions: Add endpoint to get Schemas (#108864) Return the SQL schema for all DS queries in request (to provide information to AI / Autocomplete for SQL expressions). All DS queries are treated as if they were inputs to SQL expressions in terms of conversion, regardless if they are selected in a query or not. Requires feature toggle queryService = true Endpoint is apis/query.grafana.app/v0alpha1/namespaces/default/sqlschemas --------- Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com> --- pkg/apis/query/v0alpha1/query.go | 9 + .../query/v0alpha1/zz_generated.deepcopy.go | 26 ++ .../query/v0alpha1/zz_generated.openapi.go | 27 ++ pkg/expr/sql/frame_table.go | 4 +- pkg/expr/sql_schema.go | 231 ++++++++++++++++++ pkg/registry/apis/query/query.go | 79 ++++-- pkg/registry/apis/query/register.go | 3 + pkg/registry/apis/query/sql_schema.go | 170 +++++++++++++ pkg/services/apiserver/builder/helper.go | 6 + .../public_dashboard_service_mock.go | 7 + .../publicdashboards/publicdashboard.go | 4 + .../publicdashboards/service/service.go | 5 + pkg/services/query/expr_sql_schema.go | 75 ++++++ pkg/services/query/query.go | 2 + pkg/services/query/query_service_mock.go | 6 + 15 files changed, 630 insertions(+), 24 deletions(-) create mode 100644 pkg/expr/sql_schema.go create mode 100644 pkg/registry/apis/query/sql_schema.go create mode 100644 pkg/services/query/expr_sql_schema.go diff --git a/pkg/apis/query/v0alpha1/query.go b/pkg/apis/query/v0alpha1/query.go index ef40c17ff45..f6ad172c348 100644 --- a/pkg/apis/query/v0alpha1/query.go +++ b/pkg/apis/query/v0alpha1/query.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" + "github.com/grafana/grafana/pkg/expr" ) // Generic query request with shared time across all values @@ -28,6 +29,14 @@ type QueryDataResponse struct { backend.QueryDataResponse `json:",inline"` } +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type SQLSchemas struct { + metav1.TypeMeta `json:",inline"` + + // Backend wrapper (external dependency) + expr.SQLSchemas `json:"sqlSchemas,inline"` +} + // GetResponseCode return the right status code for the response by checking the responses. func GetResponseCode(rsp *backend.QueryDataResponse) int { if rsp == nil { diff --git a/pkg/apis/query/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/query/v0alpha1/zz_generated.deepcopy.go index 38a7bd0b115..ad159a4a6cb 100644 --- a/pkg/apis/query/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/query/v0alpha1/zz_generated.deepcopy.go @@ -262,3 +262,29 @@ func (in *QueryTypeDefinitionList) DeepCopyObject() runtime.Object { } return nil } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SQLSchemas) DeepCopyInto(out *SQLSchemas) { + *out = *in + out.TypeMeta = in.TypeMeta + out.SQLSchemas = in.SQLSchemas.DeepCopy() + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQLSchemas. +func (in *SQLSchemas) DeepCopy() *SQLSchemas { + if in == nil { + return nil + } + out := new(SQLSchemas) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SQLSchemas) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/pkg/apis/query/v0alpha1/zz_generated.openapi.go b/pkg/apis/query/v0alpha1/zz_generated.openapi.go index 2ee9ed95395..68f42312ce9 100644 --- a/pkg/apis/query/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/query/v0alpha1/zz_generated.openapi.go @@ -23,6 +23,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryDataResponse": schema_pkg_apis_query_v0alpha1_QueryDataResponse(ref), "github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryTypeDefinition": schema_pkg_apis_query_v0alpha1_QueryTypeDefinition(ref), "github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryTypeDefinitionList": schema_pkg_apis_query_v0alpha1_QueryTypeDefinitionList(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1.SQLSchemas": schema_pkg_apis_query_v0alpha1_SQLSchemas(ref), } } @@ -482,3 +483,29 @@ func schema_pkg_apis_query_v0alpha1_QueryTypeDefinitionList(ref common.Reference "github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryTypeDefinition", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } + +func schema_pkg_apis_query_v0alpha1_SQLSchemas(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} diff --git a/pkg/expr/sql/frame_table.go b/pkg/expr/sql/frame_table.go index 2ac897b5007..245efc6e655 100644 --- a/pkg/expr/sql/frame_table.go +++ b/pkg/expr/sql/frame_table.go @@ -30,7 +30,7 @@ func (ft *FrameTable) String() string { return ft.Name() } -func schemaFromFrame(frame *data.Frame) mysql.Schema { +func SchemaFromFrame(frame *data.Frame) mysql.Schema { schema := make(mysql.Schema, len(frame.Fields)) for i, field := range frame.Fields { @@ -48,7 +48,7 @@ func schemaFromFrame(frame *data.Frame) mysql.Schema { // Schema implements the mysql.Table interface func (ft *FrameTable) Schema() mysql.Schema { if ft.schema == nil { - ft.schema = schemaFromFrame(ft.Frame) + ft.schema = SchemaFromFrame(ft.Frame) } return ft.schema } diff --git a/pkg/expr/sql_schema.go b/pkg/expr/sql_schema.go new file mode 100644 index 00000000000..c3851eed8bd --- /dev/null +++ b/pkg/expr/sql_schema.go @@ -0,0 +1,231 @@ +package expr + +import ( + "context" + "reflect" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/expr/mathexp" + "github.com/grafana/grafana/pkg/expr/sql" +) + +// BasicColumn represents the column type for data that is input to a SQL expression. +type BasicColumn struct { + Name string `json:"name"` + MySQLType string `json:"mysqlType"` + Nullable bool `json:"nullable"` + DataFrameFieldType data.FieldType `json:"dataFrameFieldType"` +} + +// SchemaInfo provides information and some sample data for data that could be an input +// to a SQL expression. +type SchemaInfo struct { + Columns []BasicColumn `json:"columns"` + SampleRows [][]any `json:"sampleRows"` + Error string `json:"error,omitempty"` +} + +// SQLSchemas returns info about what the Schema for a DS query will be like if the +// query were to be used an input to SQL expressions. So effectively post SQL expressions input +// conversion. +// There is a a manual DeepCopy at the end of this file that will need to be updated when this our the +// underlying structs are change. The hack script will also need to be run to update the Query service API +// generated types. +type SQLSchemas map[string]SchemaInfo + +// GetSQLSchemas returns what the schemas are for SQL expressions for all DS queries +// in the request. It executes the queries to get the schemas. +// Intended use is for autocomplete and AI, so used during the authoring/editing experience only. +func (s *Service) GetSQLSchemas(ctx context.Context, req Request) (SQLSchemas, error) { + // Extract DS Nodes and Execute Them + // Building the pipeline is maybe not best, as it can have more errors. + filtered := make([]Query, 0, len(req.Queries)) + for _, q := range req.Queries { + if NodeTypeFromDatasourceUID(q.DataSource.UID) == TypeDatasourceNode { + filtered = append(filtered, q) + } + } + req.Queries = filtered + pipeline, err := s.buildPipeline(ctx, &req) + if err != nil { + return nil, err + } + + var schemas = make(SQLSchemas) + + for _, node := range pipeline { + // For now, execute calls convert at the end, so we are being lazy and running the full conversion. Longer run we want to run without + // full conversion and just get the schema. Maybe conversion should be + dsNode := node.(*DSNode) + // Make all input to SQL + dsNode.isInputToSQLExpr = true + + // TODO: check where time is coming from, don't recall + res, err := dsNode.Execute(ctx, time.Now(), mathexp.Vars{}, s) + if err != nil { + schemas[dsNode.RefID()] = SchemaInfo{Error: err.Error()} + continue + // we want to continue and get the schemas we can + } + if res.Error != nil { + schemas[dsNode.RefID()] = SchemaInfo{Error: res.Error.Error()} + continue + // we want to continue and get the schemas we can + } + + frames := res.Values.AsDataFrames(dsNode.RefID()) + if len(frames) == 0 { + schemas[dsNode.RefID()] = SchemaInfo{Error: "no data"} + } + frame := frames[0] + + schema := sql.SchemaFromFrame(frame) + columns := make([]BasicColumn, 0, len(schema)) + for _, col := range schema { + fT, _ := sql.MySQLColToFieldType(col) + columns = append(columns, BasicColumn{ + Name: col.Name, + MySQLType: col.Type.String(), + Nullable: col.Nullable, + DataFrameFieldType: fT, + }) + } + + // Cap at 3 rows. + const maxRows = 3 + n := frame.Rows() + if n > maxRows { + n = maxRows + } + sampleRows := make([][]any, 0, n) + for i := 0; i < n; i++ { + sampleRows = append(sampleRows, frame.RowCopy(i)) + } + + schemas[dsNode.RefID()] = SchemaInfo{Columns: columns, SampleRows: sampleRows} + } + + return schemas, nil +} + +// DeepCopy returns a deep copy of the schema. +// Used AI to make it, the kubernetes one doesn't like any or interface{} +func (s SQLSchemas) DeepCopy() SQLSchemas { + if s == nil { + return nil + } + out := make(SQLSchemas, len(s)) + for k, v := range s { + out[k] = SchemaInfo{ + Columns: copyColumns(v.Columns), + SampleRows: deepCopySampleRows2D(v.SampleRows), + Error: v.Error, + } + } + return out +} + +func copyColumns(in []BasicColumn) []BasicColumn { + if in == nil { + return nil + } + out := make([]BasicColumn, len(in)) + copy(out, in) // BasicColumn is value-only, so this suffices + return out +} + +// Deep-copy [][]any preserving nil vs empty slices and cloning elements. +func deepCopySampleRows2D(in [][]any) [][]any { + if in == nil { + return nil + } + out := make([][]any, len(in)) + for i, row := range in { + if row == nil { + // preserve nil inner slice + continue + } + newRow := make([]any, len(row)) + for j, v := range row { + newRow[j] = deepCopyAny(v) + } + out[i] = newRow + } + return out +} + +// Recursively clone pointers, maps, slices, arrays, and interfaces. +// Structs are copied by value (shallow for their internals). +func deepCopyAny(v any) any { + if v == nil { + return nil + } + return deepCopyRV(reflect.ValueOf(v)).Interface() +} + +func deepCopyRV(rv reflect.Value) reflect.Value { + if !rv.IsValid() { + return rv + } + + switch rv.Kind() { + case reflect.Ptr: + if rv.IsNil() { + return rv + } + elemCopy := deepCopyRV(rv.Elem()) + newPtr := reflect.New(rv.Type().Elem()) + if elemCopy.Type().AssignableTo(newPtr.Elem().Type()) { + newPtr.Elem().Set(elemCopy) + } else if elemCopy.Type().ConvertibleTo(newPtr.Elem().Type()) { + newPtr.Elem().Set(elemCopy.Convert(newPtr.Elem().Type())) + } else { + newPtr.Elem().Set(rv.Elem()) // fallback: shallow + } + return newPtr + + case reflect.Interface: + if rv.IsNil() { + return rv + } + return deepCopyRV(rv.Elem()) + + case reflect.Map: + if rv.IsNil() { + return reflect.Zero(rv.Type()) + } + newMap := reflect.MakeMapWithSize(rv.Type(), rv.Len()) + for _, k := range rv.MapKeys() { + newMap.SetMapIndex(deepCopyRV(k), deepCopyRV(rv.MapIndex(k))) + } + return newMap + + case reflect.Slice: + if rv.IsNil() { + return reflect.Zero(rv.Type()) + } + n := rv.Len() + newSlice := reflect.MakeSlice(rv.Type(), n, n) + for i := 0; i < n; i++ { + newSlice.Index(i).Set(deepCopyRV(rv.Index(i))) + } + return newSlice + + case reflect.Array: + n := rv.Len() + newArr := reflect.New(rv.Type()).Elem() + for i := 0; i < n; i++ { + newArr.Index(i).Set(deepCopyRV(rv.Index(i))) + } + return newArr + + case reflect.Struct: + // Value copy (OK unless the struct contains references you also want deep-copied). + return rv + + default: + // Scalars (string, bool, numbers), etc. + return rv + } +} diff --git a/pkg/registry/apis/query/query.go b/pkg/registry/apis/query/query.go index f63cb850bc7..30a1ae6b567 100644 --- a/pkg/registry/apis/query/query.go +++ b/pkg/registry/apis/query/query.go @@ -241,25 +241,40 @@ func (r *queryREST) Connect(connectCtx context.Context, name string, _ runtime.O }), nil } -func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuilder, httpreq *http.Request, responder responderWrapper, connectLogger log.Logger) (*backend.QueryDataResponse, error) { - var jsonQueries = make([]*simplejson.Json, 0, len(raw.Queries)) - for _, query := range raw.Queries { - dsRef, err := getValidDataSourceRef(ctx, query.Datasource, query.DatasourceID, b.legacyDatasourceLookup) - if err != nil { - connectLogger.Error("error getting valid datasource ref", err) - } - if dsRef != nil { - query.Datasource = dsRef +type preparedQuery struct { + mReq dtos.MetricRequest + cache datasources.CacheService + headers map[string]string + logger log.Logger + builder dsquerierclient.QSDatasourceClientBuilder + exprSvc *expr.Service + reportMetrics func() +} + +func prepareQuery( + ctx context.Context, + raw query.QueryDataRequest, + b QueryAPIBuilder, + httpreq *http.Request, + connectLogger log.Logger, +) (*preparedQuery, error) { + // Normalize DS refs and build []*simplejson.Json + jsonQueries := make([]*simplejson.Json, 0, len(raw.Queries)) + for _, q := range raw.Queries { + if dsRef, derr := getValidDataSourceRef(ctx, q.Datasource, q.DatasourceID, b.legacyDatasourceLookup); derr != nil { + connectLogger.Error("error getting valid datasource ref", "err", derr) + } else if dsRef != nil { + q.Datasource = dsRef } - jsonBytes, err := json.Marshal(query) + jsonBytes, err := json.Marshal(q) if err != nil { - connectLogger.Error("error marshalling", err) + connectLogger.Error("error marshalling query", "err", err) } - sjQuery, _ := simplejson.NewJson(jsonBytes) + sjQuery, err := simplejson.NewJson(jsonBytes) if err != nil { - connectLogger.Error("error unmarshalling", err) + connectLogger.Error("error creating simplejson for query", "err", err) } jsonQueries = append(jsonQueries, sjQuery) @@ -274,13 +289,11 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil cache := &MyCacheService{ legacy: b.legacyDatasourceLookup, } - headers := ExtractKnownHeaders(httpreq.Header) instance, err := b.instanceProvider.GetInstance(ctx, connectLogger, headers) if err != nil { connectLogger.Error("failed to get instance configuration settings", "err", err) - responder.Error(err) return nil, err } @@ -288,12 +301,14 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil dsQuerierLoggerWithSlug := instance.GetLogger() + // Datasource client qsDsClientBuilder qsDsClientBuilder := dsquerierclient.NewQsDatasourceClientBuilderWithInstance( instance, ctx, dsQuerierLoggerWithSlug, ) + // Expressions service exprService := expr.ProvideService( &setting.Cfg{ ExpressionsEnabled: instanceConfig.ExpressionsEnabled, @@ -310,17 +325,37 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil qsDsClientBuilder, ) - qdr, err := service.QueryData(ctx, dsQuerierLoggerWithSlug, cache, exprService, mReq, qsDsClientBuilder, headers) + return &preparedQuery{ + mReq: mReq, + cache: cache, + headers: headers, + logger: dsQuerierLoggerWithSlug, + builder: qsDsClientBuilder, + exprSvc: exprService, + reportMetrics: func() { instance.ReportMetrics() }, + }, nil +} - // tell the `instance` structure that it can now report - // metrics that are only reported once during a request - instance.ReportMetrics() +func handlePreparedQuery(ctx context.Context, pq *preparedQuery) (*backend.QueryDataResponse, error) { + resp, err := service.QueryData(ctx, pq.logger, pq.cache, pq.exprSvc, pq.mReq, pq.builder, pq.headers) + pq.reportMetrics() + return resp, err +} +func handleQuery( + ctx context.Context, + raw query.QueryDataRequest, + b QueryAPIBuilder, + httpreq *http.Request, + responder responderWrapper, + connectLogger log.Logger, +) (*backend.QueryDataResponse, error) { + pq, err := prepareQuery(ctx, raw, b, httpreq, connectLogger) if err != nil { - return qdr, err + responder.Error(err) + return nil, err } - - return qdr, nil + return handlePreparedQuery(ctx, pq) } type responderWrapper struct { diff --git a/pkg/registry/apis/query/register.go b/pkg/registry/apis/query/register.go index 109de13276c..221b589b511 100644 --- a/pkg/registry/apis/query/register.go +++ b/pkg/registry/apis/query/register.go @@ -161,6 +161,7 @@ func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) { &query.QueryDataResponse{}, &query.QueryTypeDefinition{}, &query.QueryTypeDefinitionList{}, + &query.SQLSchemas{}, ) } @@ -201,6 +202,8 @@ func (b *QueryAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIG // The query endpoint -- NOTE, this uses a rewrite hack to allow requests without a name parameter storage["query"] = newQueryREST(b) + storage["sqlschemas"] = newSQLSchemasREST(b) + // Register the expressions query schemas err := queryschema.RegisterQueryTypes(b.queryTypes, storage) diff --git a/pkg/registry/apis/query/sql_schema.go b/pkg/registry/apis/query/sql_schema.go new file mode 100644 index 00000000000..10ec3dc2526 --- /dev/null +++ b/pkg/registry/apis/query/sql_schema.go @@ -0,0 +1,170 @@ +package query + +import ( + "context" + "net/http" + "strconv" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/expr" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + errorsK8s "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" + + query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" + "github.com/grafana/grafana/pkg/infra/log" + service "github.com/grafana/grafana/pkg/services/query" + "github.com/grafana/grafana/pkg/web" +) + +type sqlSchemaREST struct { + logger log.Logger + builder *QueryAPIBuilder +} + +var ( + _ rest.Storage = (*sqlSchemaREST)(nil) + _ rest.SingularNameProvider = (*sqlSchemaREST)(nil) + _ rest.Connecter = (*sqlSchemaREST)(nil) + _ rest.Scoper = (*sqlSchemaREST)(nil) + _ rest.StorageMetadata = (*sqlSchemaREST)(nil) +) + +func newSQLSchemasREST(builder *QueryAPIBuilder) *sqlSchemaREST { + return &sqlSchemaREST{ + logger: log.New("query.sqlschemas"), + builder: builder, + } +} + +func (r *sqlSchemaREST) New() runtime.Object { + // This is added as the "ResponseType" regardless what ProducesObject() says :) + return &query.SQLSchemas{} +} + +func (r *sqlSchemaREST) Destroy() {} + +func (r *sqlSchemaREST) NamespaceScoped() bool { + return true +} + +func (r *sqlSchemaREST) GetSingularName() string { + return "SQLSchema" // Used for the +} + +func (r *sqlSchemaREST) ProducesMIMETypes(verb string) []string { + return []string{"application/json"} // and parquet! +} + +func (r *sqlSchemaREST) ProducesObject(verb string) interface{} { + return &query.SQLSchemas{} +} + +func (r *sqlSchemaREST) ConnectMethods() []string { + return []string{"POST"} +} + +func (r *sqlSchemaREST) NewConnectOptions() (runtime.Object, bool, string) { + return nil, false, "" // true means you can use the trailing path as a variable +} + +// called by mt query service and also when queryServiceFromUI is enabled, can be both mt and st +func (r *sqlSchemaREST) Connect(connectCtx context.Context, name string, _ runtime.Object, incomingResponder rest.Responder) (http.Handler, error) { + // See: /pkg/services/apiserver/builder/helper.go#L34 + // The name is set with a rewriter hack + if name != "name" { + r.logger.Debug("Connect name is not name") + return nil, errorsK8s.NewNotFound(schema.GroupResource{}, name) + } + b := r.builder + + return http.HandlerFunc(func(w http.ResponseWriter, httpreq *http.Request) { + ctx, span := b.tracer.Start(httpreq.Context(), "QueryService.GetSQLSchemas") + defer span.End() + ctx = request.WithNamespace(ctx, request.NamespaceValue(connectCtx)) + traceId := span.SpanContext().TraceID() + connectLogger := b.log.New("traceId", traceId.String(), "rule_uid", httpreq.Header.Get("X-Rule-Uid")) + responder := newResponderWrapper(incomingResponder, + func(statusCode *int, obj runtime.Object) { + if *statusCode/100 == 4 { + span.SetStatus(codes.Error, strconv.Itoa(*statusCode)) + } + + if *statusCode >= 500 { + o, ok := obj.(*query.QueryDataResponse) + if ok && o.Responses != nil { + for refId, response := range o.Responses { + if response.ErrorSource == backend.ErrorSourceDownstream { + *statusCode = http.StatusBadRequest //force this to be a 400 since it's downstream + span.SetStatus(codes.Error, strconv.Itoa(*statusCode)) + span.SetAttributes(attribute.String("error.source", "downstream")) + break + } else if response.Error != nil { + connectLogger.Debug("500 error without downstream error source", "error", response.Error, "errorSource", response.ErrorSource, "refId", refId) + span.SetStatus(codes.Error, "500 error without downstream error source") + } else { + span.SetStatus(codes.Error, "500 error without downstream error source and no Error message") + span.SetAttributes(attribute.String("error.ref_id", refId)) + } + } + } + } + }, + + func(err error) { + connectLogger.Error("error caught in handler", "err", err) + span.SetStatus(codes.Error, "query error") + + if err == nil { + return + } + + span.RecordError(err) + }) + + raw := &query.QueryDataRequest{} + err := web.Bind(httpreq, raw) + if err != nil { + connectLogger.Error("Hit unexpected error when reading query", "err", err) + err = errorsK8s.NewBadRequest("error reading query") + responder.Error(err) + return + } + + qdr, err := handleSQLSchemaQuery(ctx, *raw, *b, httpreq, *responder, connectLogger) + if err != nil { + responder.Error(err) + return + } + + responder.Object(200, &query.SQLSchemas{ + SQLSchemas: qdr, + }) + }), nil +} + +func handlePreparedSQLSchema(ctx context.Context, pq *preparedQuery) (expr.SQLSchemas, error) { + resp, err := service.GetSQLSchemas(ctx, pq.logger, pq.cache, pq.exprSvc, pq.mReq, pq.builder, pq.headers) + pq.reportMetrics() + return resp, err +} + +func handleSQLSchemaQuery( + ctx context.Context, + raw query.QueryDataRequest, + b QueryAPIBuilder, + httpreq *http.Request, + responder responderWrapper, + connectLogger log.Logger, +) (expr.SQLSchemas, error) { + pq, err := prepareQuery(ctx, raw, b, httpreq, connectLogger) + if err != nil { + responder.Error(err) + return nil, err + } + return handlePreparedSQLSchema(ctx, pq) +} diff --git a/pkg/services/apiserver/builder/helper.go b/pkg/services/apiserver/builder/helper.go index 222637401ee..cfcf3540b43 100644 --- a/pkg/services/apiserver/builder/helper.go +++ b/pkg/services/apiserver/builder/helper.go @@ -58,6 +58,12 @@ var PathRewriters = []filters.PathRewriter{ return matches[1] + "/name" // connector requires a name }, }, + { + Pattern: regexp.MustCompile(`(/apis/query.grafana.app/v0alpha1/namespaces/.*/sqlschemas$)`), + ReplaceFunc: func(matches []string) string { + return matches[1] + "/name" // connector requires a name + }, + }, { Pattern: regexp.MustCompile(`(/apis/.*/v0alpha1/namespaces/.*/queryconvert$)`), ReplaceFunc: func(matches []string) string { diff --git a/pkg/services/publicdashboards/public_dashboard_service_mock.go b/pkg/services/publicdashboards/public_dashboard_service_mock.go index b54ef3ec6db..c2b39355737 100644 --- a/pkg/services/publicdashboards/public_dashboard_service_mock.go +++ b/pkg/services/publicdashboards/public_dashboard_service_mock.go @@ -4,9 +4,12 @@ package publicdashboards import ( context "context" + "fmt" backend "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/expr" dashboards "github.com/grafana/grafana/pkg/services/dashboards" dtos "github.com/grafana/grafana/pkg/api/dtos" @@ -587,6 +590,10 @@ func (_m *FakePublicDashboardService) Update(ctx context.Context, u *user.Signed return r0, r1 } +func (_m *FakePublicDashboardService) GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) { + return nil, fmt.Errorf("not implemented in public dashboards") +} + // NewFakePublicDashboardService creates a new instance of FakePublicDashboardService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewFakePublicDashboardService(t interface { diff --git a/pkg/services/publicdashboards/publicdashboard.go b/pkg/services/publicdashboards/publicdashboard.go index c3c2eb83496..b31657ef267 100644 --- a/pkg/services/publicdashboards/publicdashboard.go +++ b/pkg/services/publicdashboards/publicdashboard.go @@ -5,6 +5,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/expr" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/dashboards" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" @@ -37,6 +39,8 @@ type Service interface { ExistsEnabledByAccessToken(ctx context.Context, accessToken string) (bool, error) ExistsEnabledByDashboardUid(ctx context.Context, dashboardUid string) (bool, error) + + GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) } // ServiceWrapper these methods have different behavior between OSS and Enterprise. The latter would call the OSS service first diff --git a/pkg/services/publicdashboards/service/service.go b/pkg/services/publicdashboards/service/service.go index 239ce31829c..bb3c70dcef4 100644 --- a/pkg/services/publicdashboards/service/service.go +++ b/pkg/services/publicdashboards/service/service.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -512,6 +513,10 @@ func (pd *PublicDashboardServiceImpl) logIsEnabledChanged(existingPubdash *Publi } } +func (pd *PublicDashboardServiceImpl) GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) { + return nil, fmt.Errorf("sql schema endpoint not supported with public dashboards") +} + // Checks to see if PublicDashboard.ExistsEnabledByDashboardUid is true on create or changed on update func publicDashboardIsEnabledChanged(existingPubdash *PublicDashboard, newPubdash *PublicDashboard) bool { // creating dashboard, enabled true diff --git a/pkg/services/query/expr_sql_schema.go b/pkg/services/query/expr_sql_schema.go new file mode 100644 index 00000000000..db29da98625 --- /dev/null +++ b/pkg/services/query/expr_sql_schema.go @@ -0,0 +1,75 @@ +package query + +import ( + "context" + + "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/apimachinery/errutil" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/expr" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/dsquerierclient" + "github.com/grafana/grafana/pkg/services/validations" +) + +func (s *ServiceImpl) GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) { + //TODO DEdupe code + + parsedReq, err := s.parseMetricRequest(ctx, user, false, reqDTO, false) + if err != nil { + return expr.SQLSchemas{}, err + } + exprReq := expr.Request{ + Queries: []expr.Query{}, + } + + if user != nil { // for passthrough authentication, SSE does not authenticate + exprReq.User = user + exprReq.OrgId = user.GetOrgID() + } + + for _, pq := range parsedReq.getFlattenedQueries() { + if pq.datasource == nil { + return nil, ErrMissingDataSourceInfo.Build(errutil.TemplateData{ + Public: map[string]any{ + "RefId": pq.query.RefID, + }, + }) + } + + exprReq.Queries = append(exprReq.Queries, expr.Query{ + JSON: pq.query.JSON, + Interval: pq.query.Interval, + RefID: pq.query.RefID, + MaxDataPoints: pq.query.MaxDataPoints, + QueryType: pq.query.QueryType, + DataSource: pq.datasource, + TimeRange: expr.AbsoluteTimeRange{ + From: pq.query.TimeRange.From, + To: pq.query.TimeRange.To, + }, + }) + } + + return s.expressionService.GetSQLSchemas(ctx, exprReq) +} + +func GetSQLSchemas(ctx context.Context, log log.Logger, dscache datasources.CacheService, exprService *expr.Service, reqDTO dtos.MetricRequest, qsDatasourceClientBuilder dsquerierclient.QSDatasourceClientBuilder, headers map[string]string) (expr.SQLSchemas, error) { + s := &ServiceImpl{ + log: log, + dataSourceCache: dscache, + expressionService: exprService, + dataSourceRequestValidator: validations.ProvideValidator(), + qsDatasourceClientBuilder: qsDatasourceClientBuilder, + headers: headers, + concurrentQueryLimit: 16, // TODO: make it configurable + } + + user, err := identity.GetRequester(ctx) + if err != nil { + return nil, err + } + + return s.GetSQLSchemas(ctx, user, reqDTO) +} diff --git a/pkg/services/query/query.go b/pkg/services/query/query.go index d69c69af134..138812ebd03 100644 --- a/pkg/services/query/query.go +++ b/pkg/services/query/query.go @@ -73,6 +73,8 @@ type Service interface { // this is more "forward compatible", for example supports per-query time ranges QueryDataNew(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) + + GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) } // Gives us compile time error if the service does not adhere to the contract of the interface diff --git a/pkg/services/query/query_service_mock.go b/pkg/services/query/query_service_mock.go index 22c08118941..c391b86061f 100644 --- a/pkg/services/query/query_service_mock.go +++ b/pkg/services/query/query_service_mock.go @@ -4,10 +4,12 @@ package query import ( context "context" + "fmt" backend "github.com/grafana/grafana-plugin-sdk-go/backend" dtos "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/expr" identity "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -97,6 +99,10 @@ func (_m *FakeQueryService) Run(ctx context.Context) error { return r0 } +func (_m *FakeQueryService) GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) { + return nil, fmt.Errorf("sql schema endpoint not supported with public dashboards") +} + // NewFakeQueryService creates a new instance of FakeQueryService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewFakeQueryService(t interface { From 92375279f70485dd56e3fb75e25d43a8937b11cc Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 30 Oct 2025 14:22:44 +0000 Subject: [PATCH 52/72] Slider: Expose prop to control visibility of input (#113084) expose prop to control visibility of slider input --- .../src/components/Slider/Slider.test.tsx | 12 +++++++++ .../src/components/Slider/Slider.tsx | 25 +++++++++++-------- .../grafana-ui/src/components/Slider/types.ts | 2 ++ 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/grafana-ui/src/components/Slider/Slider.test.tsx b/packages/grafana-ui/src/components/Slider/Slider.test.tsx index e24ccd7e751..1fb4ab1c03a 100644 --- a/packages/grafana-ui/src/components/Slider/Slider.test.tsx +++ b/packages/grafana-ui/src/components/Slider/Slider.test.tsx @@ -33,6 +33,18 @@ describe('Slider', () => { expect(sliderInput).toHaveValue('10'); }); + it('hides the slider input if showInput is false', () => { + render(); + + const slider = screen.getByRole('slider'); + const sliderInput = screen.queryByRole('textbox'); + + expect(slider).toHaveAttribute('aria-valuemin', '10'); + expect(slider).toHaveAttribute('aria-valuemax', '20'); + expect(slider).toHaveAttribute('aria-valuenow', '10'); + expect(sliderInput).not.toBeInTheDocument(); + }); + it('renders correct contents with a value', () => { render(); diff --git a/packages/grafana-ui/src/components/Slider/Slider.tsx b/packages/grafana-ui/src/components/Slider/Slider.tsx index b41f41d0af7..d8d310fa92b 100644 --- a/packages/grafana-ui/src/components/Slider/Slider.tsx +++ b/packages/grafana-ui/src/components/Slider/Slider.tsx @@ -29,6 +29,7 @@ export const Slider = ({ marks, included, inputId, + showInput = true, }: SliderProps) => { const isHorizontal = orientation === 'horizontal'; const styles = useStyles2(getStyles, isHorizontal, Boolean(marks)); @@ -114,17 +115,19 @@ export const Slider = ({ included={included} /> - + {showInput && ( + + )}
); diff --git a/packages/grafana-ui/src/components/Slider/types.ts b/packages/grafana-ui/src/components/Slider/types.ts index 812a8bed7d7..880bfd2f712 100644 --- a/packages/grafana-ui/src/components/Slider/types.ts +++ b/packages/grafana-ui/src/components/Slider/types.ts @@ -14,6 +14,8 @@ interface CommonSliderProps { marks?: SliderMarks; /** If the value is true, it means a continuous value interval, otherwise, it is a independent value. */ included?: boolean; + /** Controls visibility of the input field. Defaults to true. */ + showInput?: boolean; } export interface SliderProps extends CommonSliderProps { value?: number; From 63c5d8cb8f1c5432c7ed35569785f669d5b3bda8 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 30 Oct 2025 14:23:03 +0000 Subject: [PATCH 53/72] Collapse: Improve layout and deprecate `collapsible` prop (#113164) * deprecate collapsible prop, improve Collapse to allow for buttons in the header * add ariaLabel * Revert "add ariaLabel" This reverts commit a903a0da5da70065db31f4a98fb6caf0f6f93727. * add aria-labelledby --- .../src/querybuilder/QueryPatternsModal.tsx | 1 - .../querybuilder/shared/QueryOptionGroup.tsx | 1 - .../components/Collapse/Collapse.story.tsx | 39 ++++++++++- .../src/components/Collapse/Collapse.test.tsx | 2 +- .../src/components/Collapse/Collapse.tsx | 65 +++++++++---------- .../src/components/IconButton/IconButton.tsx | 7 +- .../import-to-gma/ConfirmConvertModal.tsx | 1 - .../import-to-gma/ImportToGMARules.tsx | 1 - .../EditDefaultPolicyForm.tsx | 1 - .../features/explore/CorrelationHelper.tsx | 2 - .../features/explore/Logs/LogsContainer.tsx | 6 +- .../features/explore/Logs/LogsSamplePanel.tsx | 1 - .../SpanFilters/SpanFilters.tsx | 2 +- .../logs/components/panel/LogLineContext.tsx | 1 - .../panel/LogLineDetailsComponent.tsx | 7 -- .../data-hover/DataHoverRows.tsx | 1 - .../LogsQueryEditor/AzureCheatSheet.tsx | 1 - .../ResourcePicker/AdvancedMulti.tsx | 1 - .../components/CheatSheet/LogsCheatSheet.tsx | 2 +- .../configuration/ConfigurationEditor.tsx | 2 +- .../QueryEditor/QueryOptionGroup.tsx | 1 - .../loki/components/LokiContextUi.tsx | 1 - .../components/QueryPatternsModal.tsx | 1 - .../configuration/ConfigurationEditor.tsx | 2 +- .../prometheus/QueryOptionGroup.tsx | 1 - 25 files changed, 83 insertions(+), 67 deletions(-) diff --git a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx index a4f17087b21..2364bc80a86 100644 --- a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx +++ b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx @@ -111,7 +111,6 @@ export const QueryPatternsModal = (props: Props) => { } )} isOpen={isOpen} - collapsible={true} onToggle={() => { const action = isOpen ? 'close' : 'open'; reportInteraction(`grafana_prom_kickstart_toggle_pattern_card`, { diff --git a/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx b/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx index 554201a820d..8253d195ea0 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx @@ -20,7 +20,6 @@ export function QueryOptionGroup({ title, children, collapsedInfo }: Props) {
= { children: 'Panel data', isOpen: false, label: 'Collapse panel', - collapsible: true, }, argTypes: { onToggle: { action: 'toggled' }, @@ -57,4 +59,39 @@ Controlled.parameters = { }, }; +export const WithCustomLabel: StoryFn = (args) => { + const [, updateArgs] = useArgs(); + return ( + { + action('onToggle')({ isOpen: !args.isOpen }); + updateArgs({ isOpen: !args.isOpen }); + }} + label={ + + Collapse panel + + { + event.stopPropagation(); + action('onDeleteClick')(); + }} + aria-label="Delete" + name="trash-alt" + /> + + + } + > +

{args.children}

+
+ ); +}; +WithCustomLabel.parameters = { + controls: { + exclude: [...EXCLUDED_PROPS, 'label'], + }, +}; + export default meta; diff --git a/packages/grafana-ui/src/components/Collapse/Collapse.test.tsx b/packages/grafana-ui/src/components/Collapse/Collapse.test.tsx index 432ff8ef56e..817df10153d 100644 --- a/packages/grafana-ui/src/components/Collapse/Collapse.test.tsx +++ b/packages/grafana-ui/src/components/Collapse/Collapse.test.tsx @@ -42,7 +42,7 @@ describe('Collapse', () => { const onToggle = jest.fn(); const { user } = setup( - +
{contentText}
); diff --git a/packages/grafana-ui/src/components/Collapse/Collapse.tsx b/packages/grafana-ui/src/components/Collapse/Collapse.tsx index d0f8757e1ca..195ea24b4d1 100644 --- a/packages/grafana-ui/src/components/Collapse/Collapse.tsx +++ b/packages/grafana-ui/src/components/Collapse/Collapse.tsx @@ -1,12 +1,11 @@ import { css, cx } from '@emotion/css'; -import { useState } from 'react'; +import { useId, useState } from 'react'; import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '../../themes/ThemeContext'; -import { clearButtonStyles } from '../Button/Button'; -import { Icon } from '../Icon/Icon'; +import { IconButton } from '../IconButton/IconButton'; const getStyles = (theme: GrafanaTheme2) => ({ collapse: css({ @@ -74,25 +73,21 @@ const getStyles = (theme: GrafanaTheme2) => ({ }, }), header: css({ + cursor: 'pointer', label: 'collapse__header', - padding: theme.spacing(1, 2, 1, 2), + padding: theme.spacing(1), display: 'flex', + gap: theme.spacing(1), }), - headerCollapsed: css({ - label: 'collapse__header--collapsed', - padding: theme.spacing(1, 2, 1, 2), + button: css({ + marginRight: 0, }), headerLabel: css({ label: 'collapse__header-label', fontWeight: theme.typography.fontWeightMedium, - marginRight: theme.spacing(1), fontSize: theme.typography.size.md, display: 'flex', - flex: '0 0 100%', - }), - icon: css({ - label: 'collapse__icon', - margin: theme.spacing(0.25, 1, 0, -1), + flex: 1, }), }); @@ -103,12 +98,12 @@ export interface Props { label: React.ReactNode; /** Indicates loading state of the content */ loading?: boolean; - /** Toggle collapsed header icon */ - collapsible?: boolean; /** Callback for the toggle functionality */ onToggle?: (isOpen: boolean) => void; /** Additional class name for the root element */ className?: string; + /** @deprecated this prop is no longer used and will be removed in Grafana 13 */ + collapsible?: boolean; } export const ControlledCollapse = ({ isOpen, onToggle, ...otherProps }: React.PropsWithChildren) => { @@ -116,7 +111,6 @@ export const ControlledCollapse = ({ isOpen, onToggle, ...otherProps }: React.Pr return ( { setOpen(!open); @@ -133,35 +127,38 @@ export const ControlledCollapse = ({ isOpen, onToggle, ...otherProps }: React.Pr * * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-collapse--docs */ -export const Collapse = ({ - isOpen, - label, - loading, - collapsible, - onToggle, - className, - children, -}: React.PropsWithChildren) => { - const buttonStyles = useStyles2(clearButtonStyles); +export const Collapse = ({ isOpen, label, loading, onToggle, className, children }: React.PropsWithChildren) => { const style = useStyles2(getStyles); + const labelId = useId(); + const contentId = useId(); + const onClickToggle = () => { if (onToggle) { onToggle(!isOpen); } }; - const panelClass = cx([style.collapse, className]); - const loaderClass = loading ? cx([style.loader, style.loaderActive]) : cx([style.loader]); - const headerClass = collapsible ? cx([style.header]) : cx([style.headerCollapsed]); + const loaderClass = loading ? cx([style.loader, style.loaderActive]) : style.loader; return (
- + {/* the inner button handles keyboard a11y. this is a convenience for mouse users */} + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} +
+ +
+ {label} +
+
{isOpen && ( -
+
{children}
diff --git a/packages/grafana-ui/src/components/IconButton/IconButton.tsx b/packages/grafana-ui/src/components/IconButton/IconButton.tsx index d4d77c33fab..f2d44eb2fa1 100644 --- a/packages/grafana-ui/src/components/IconButton/IconButton.tsx +++ b/packages/grafana-ui/src/components/IconButton/IconButton.tsx @@ -41,7 +41,12 @@ interface BasePropsWithAriaLabel extends BaseProps { ['aria-label']: string; } -export type Props = BasePropsWithTooltip | BasePropsWithAriaLabel; +interface BasePropsWithAriaLabelledBy extends BaseProps { + /** Reference to an element id that labels the button. No tooltip will be set in this case. */ + ['aria-labelledby']: string; +} + +export type Props = BasePropsWithTooltip | BasePropsWithAriaLabel | BasePropsWithAriaLabelledBy; /** * This component looks just like an icon but behaves like a button. diff --git a/public/app/features/alerting/unified/components/import-to-gma/ConfirmConvertModal.tsx b/public/app/features/alerting/unified/components/import-to-gma/ConfirmConvertModal.tsx index 4f2c364859a..bf2a253d7e8 100644 --- a/public/app/features/alerting/unified/components/import-to-gma/ConfirmConvertModal.tsx +++ b/public/app/features/alerting/unified/components/import-to-gma/ConfirmConvertModal.tsx @@ -385,7 +385,6 @@ function TargetFolderNotEmptyWarning({ targetFolderRules }: { targetFolderRules: )} isOpen={showTargetRules} onToggle={toggleShowTargetRules} - collapsible={true} > diff --git a/public/app/features/alerting/unified/components/import-to-gma/ImportToGMARules.tsx b/public/app/features/alerting/unified/components/import-to-gma/ImportToGMARules.tsx index bfdb5b3303c..d88d446642d 100644 --- a/public/app/features/alerting/unified/components/import-to-gma/ImportToGMARules.tsx +++ b/public/app/features/alerting/unified/components/import-to-gma/ImportToGMARules.tsx @@ -150,7 +150,6 @@ const ImportToGMARules = () => { label={t('alerting.import-to-gma.additional-settings', 'Additional settings')} isOpen={optionsShowing} onToggle={toggleOptions} - collapsible={true} > diff --git a/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx index dc87060c03d..61ce391d779 100644 --- a/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx @@ -130,7 +130,6 @@ export const AmRootRouteForm = ({ actionButtons, alertManagerSourceName, onSubmi /> { })} { setIsLabelDescOpen(!isLabelDescOpen); @@ -201,7 +200,6 @@ export const CorrelationHelper = ({ exploreId, correlations }: Props) => { { setIsTransformOpen(!isTransformOpen); diff --git a/public/app/features/explore/Logs/LogsContainer.tsx b/public/app/features/explore/Logs/LogsContainer.tsx index 6ff6c9a248c..13bc78b5849 100644 --- a/public/app/features/explore/Logs/LogsContainer.tsx +++ b/public/app/features/explore/Logs/LogsContainer.tsx @@ -24,7 +24,7 @@ import { import { t } from '@grafana/i18n'; import { getDataSourceSrv } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; -import { Collapse } from '@grafana/ui'; +import { PanelChrome } from '@grafana/ui'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { GetFieldLinksFn } from 'app/plugins/panel/logs/types'; import { ExploreItemState } from 'app/types/explore'; @@ -309,7 +309,7 @@ class LogsContainer extends PureComponent - + {(controls) => ( )} - + } isOpen={enabled} - collapsible={true} onToggle={onToggleLogsSampleCollapse} > diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx index d9909845984..d48dabdf1fa 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx +++ b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx @@ -151,7 +151,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => { return (
- + diff --git a/public/app/features/logs/components/panel/LogLineContext.tsx b/public/app/features/logs/components/panel/LogLineContext.tsx index 59a34bb9cae..1fab2336c3d 100644 --- a/public/app/features/logs/components/panel/LogLineContext.tsx +++ b/public/app/features/logs/components/panel/LogLineContext.tsx @@ -329,7 +329,6 @@ export const LogLineContext = memo(
{getLogRowContextUi(log, updateResults)}
)} setShowLog(!showLog)} className={styles.referenceLogLine} diff --git a/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx b/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx index 240bc546313..36bdbf2bdde 100644 --- a/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx +++ b/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx @@ -131,7 +131,6 @@ export const LogLineDetailsComponent = memo( handleToggle('logLineOpen', isOpen)} > @@ -140,7 +139,6 @@ export const LogLineDetailsComponent = memo( {displayedFields.length > 0 && setDisplayedFields && ( handleToggle('displayedFieldsOpen', isOpen)} > @@ -151,7 +149,6 @@ export const LogLineDetailsComponent = memo( handleToggle('linksOpen', isOpen)} > @@ -161,7 +158,6 @@ export const LogLineDetailsComponent = memo( {trace && ( handleToggle('traceOpen', isOpen)} > @@ -174,7 +170,6 @@ export const LogLineDetailsComponent = memo( className={styles.collapsable} key={'fields'} label={t('logs.log-line-details.fields-section', 'Fields')} - collapsible isOpen={fieldsOpen} onToggle={(isOpen: boolean) => handleToggle('fieldsOpen', isOpen)} > @@ -186,7 +181,6 @@ export const LogLineDetailsComponent = memo( className={styles.collapsable} key={group} label={group} - collapsible isOpen={store.getBool(`${logOptionsStorageKey}.log-details.${groupOptionName(group)}`, true)} onToggle={(isOpen: boolean) => handleToggle(groupOptionName(group), isOpen)} > @@ -199,7 +193,6 @@ export const LogLineDetailsComponent = memo( className={styles.collapsable} key={'fields'} label={t('logs.log-line-details.fields-section', 'Fields')} - collapsible isOpen={fieldsOpen} onToggle={(isOpen: boolean) => handleToggle('fieldsOpen', isOpen)} > diff --git a/public/app/features/visualization/data-hover/DataHoverRows.tsx b/public/app/features/visualization/data-hover/DataHoverRows.tsx index d99fcfd04f9..0c9ca3759d7 100644 --- a/public/app/features/visualization/data-hover/DataHoverRows.tsx +++ b/public/app/features/visualization/data-hover/DataHoverRows.tsx @@ -38,7 +38,6 @@ export const DataHoverRows = ({ layers, activeTabIndex }: Props) => { return shouldDisplayCollapse ? ( { diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx index 5116a7b86fb..7ed4304b881 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/AzureCheatSheet.tsx @@ -186,7 +186,6 @@ const AzureCheatSheet = (props: AzureCheatSheetProps) => { return ( setAreDropdownsOpen({ ...areDropdownsOpen, [category]: isOpen })} key={category} diff --git a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/AdvancedMulti.tsx b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/AdvancedMulti.tsx index d16cfff82e9..f9d5cee1d55 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/AdvancedMulti.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ResourcePicker/AdvancedMulti.tsx @@ -19,7 +19,6 @@ const AdvancedMulti = ({ resources, onChange, renderAdvanced }: ResourcePickerPr return (
setIsAdvancedOpen(!isAdvancedOpen)} diff --git a/public/app/plugins/datasource/cloudwatch/components/CheatSheet/LogsCheatSheet.tsx b/public/app/plugins/datasource/cloudwatch/components/CheatSheet/LogsCheatSheet.tsx index 86fcbd00857..082a8eaf44a 100644 --- a/public/app/plugins/datasource/cloudwatch/components/CheatSheet/LogsCheatSheet.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/CheatSheet/LogsCheatSheet.tsx @@ -80,7 +80,7 @@ interface CollapseProps { const CheatSheetCollapse = (props: CollapseProps) => { const [isOpen, setIsOpen] = useState(false); return ( - + {props.children} ); diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/configuration/ConfigurationEditor.tsx b/public/app/plugins/datasource/grafana-postgresql-datasource/configuration/ConfigurationEditor.tsx index 8a76ff319f4..3a7ffccdbb7 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/configuration/ConfigurationEditor.tsx +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/configuration/ConfigurationEditor.tsx @@ -113,7 +113,7 @@ export const PostgresConfigEditor = (props: DataSourcePluginOptionsEditorProps

- setIsOpen((x) => !x)}> + setIsOpen((x) => !x)}> The database user should only be granted SELECT permissions on the specified database & tables you want to query.
Grafana does not validate that queries are safe so queries can contain any SQL statement. For example, diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryOptionGroup.tsx b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryOptionGroup.tsx index c6c3dad3719..df27a4b83d8 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryOptionGroup.tsx +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryOptionGroup.tsx @@ -19,7 +19,6 @@ export function QueryOptionGroup({ title, children, collapsedInfo }: Props) {

{ window.localStorage.setItem(IS_LOKI_LOG_CONTEXT_UI_OPEN, (!isOpen).toString()); diff --git a/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx b/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx index 9be0cc98ec7..7841d9f1edc 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/QueryPatternsModal.tsx @@ -99,7 +99,6 @@ export const QueryPatternsModal = (props: Props) => { key={patternType} label={`${capitalize(patternType)} query starters`} isOpen={openTabs.includes(patternType)} - collapsible={true} onToggle={() => setOpenTabs((tabs) => // close tab if it's already open, otherwise open it diff --git a/public/app/plugins/datasource/mysql/configuration/ConfigurationEditor.tsx b/public/app/plugins/datasource/mysql/configuration/ConfigurationEditor.tsx index bf0acc0731d..46cd7d8b3e3 100644 --- a/public/app/plugins/datasource/mysql/configuration/ConfigurationEditor.tsx +++ b/public/app/plugins/datasource/mysql/configuration/ConfigurationEditor.tsx @@ -60,7 +60,7 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps - setIsOpen((x) => !x)}> + setIsOpen((x) => !x)}> The database user should only be granted SELECT permissions on the specified database & tables you want to query.
Grafana does not validate that queries are safe so queries can contain any SQL statement. For example, diff --git a/public/app/plugins/datasource/tempo/_importedDependencies/datasources/prometheus/QueryOptionGroup.tsx b/public/app/plugins/datasource/tempo/_importedDependencies/datasources/prometheus/QueryOptionGroup.tsx index 4541016132b..dd68fa412ab 100644 --- a/public/app/plugins/datasource/tempo/_importedDependencies/datasources/prometheus/QueryOptionGroup.tsx +++ b/public/app/plugins/datasource/tempo/_importedDependencies/datasources/prometheus/QueryOptionGroup.tsx @@ -25,7 +25,6 @@ export function QueryOptionGroup({ title, children, collapsedInfo, queryStats, o
Date: Thu, 30 Oct 2025 15:31:57 +0100 Subject: [PATCH 54/72] Codegen/CI: Update makefile to also verify gen-cue (#113211) * update makefile to also verify gen-cue * update for jsonnet --- Makefile | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5bf0150e047..ab2a0ae2c55 100644 --- a/Makefile +++ b/Makefile @@ -150,7 +150,19 @@ i18n-extract: i18n-extract-enterprise ##@ Building .PHONY: gen-cue -gen-cue: ## Do all CUE/Thema code generation +gen-cue: do-gen-cue + @if [ -n "$$CODEGEN_VERIFY" ]; then \ + echo "Verifying generated code is up to date..."; \ + if ! git diff --quiet; then \ + echo "Error: Generated cue files are not up to date. Please run 'make gen-cue' to regenerate."; \ + git diff --name-only; \ + exit 1; \ + fi; \ + echo "Generated cue files are up to date."; \ + fi + +.PHONY: do-gen-cue +do-gen-cue: ## Do all CUE/Thema code generation @echo "generate code from .cue files" go generate ./kinds/gen.go go generate ./public/app/plugins/gen.go @@ -223,10 +235,24 @@ fix-cue: $(cue) fix kinds/**/*.cue $(cue) fix public/app/plugins/**/**/*.cue + .PHONY: gen-jsonnet -gen-jsonnet: +gen-jsonnet: do-gen-jsonnet + @if [ -n "$$CODEGEN_VERIFY" ]; then \ + echo "Verifying generated code is up to date..."; \ + if ! git diff --quiet; then \ + echo "Error: Generated jsonnet files are not up to date. Please run 'make gen-jsonnet' to regenerate."; \ + git diff --name-only; \ + exit 1; \ + fi; \ + echo "Generated jsonnet files are up to date."; \ + fi + +.PHONY: do-gen-jsonnet +do-gen-jsonnet: go generate ./devenv/jsonnet + .PHONY: update-workspace update-workspace: gen-go @echo "updating workspace" From 20ec8ee61cec32d756d1faf2bfd3481342e3e655 Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Thu, 30 Oct 2025 10:37:13 -0400 Subject: [PATCH 55/72] useProvisionedRequestHandler: reset ref when a new request is loading (#113196) --- .../provisioning/hooks/useProvisionedRequestHandler.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts b/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts index 528e3ee49be..a5a09fb4ce3 100644 --- a/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts +++ b/public/app/features/provisioning/hooks/useProvisionedRequestHandler.ts @@ -90,6 +90,11 @@ export function useProvisionedRequestHandler({ workflow, }; + // Reset handler guard when a new request starts loading + if (request.isLoading) { + hasHandled.current = false; + } + if (request.isError) { hasHandled.current = true; handlers.onError?.(request.error, info); From d6bcca2f7e0c90222bc141dec4b101a25179e357 Mon Sep 17 00:00:00 2001 From: Lauren <61048546+laurenashleigh@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:14:37 +0000 Subject: [PATCH 56/72] Alerting: Hide metadata if grouping by folder (#113216) * Alerting: Hide metadata if grouping by folder * resolve comments * resolve comments 2 --- .../alerting/unified/triage/Workbench.tsx | 30 +++++++++++++++---- .../unified/triage/rows/AlertRuleRow.tsx | 23 +++++++++----- .../unified/triage/scene/Workbench.tsx | 2 +- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/public/app/features/alerting/unified/triage/Workbench.tsx b/public/app/features/alerting/unified/triage/Workbench.tsx index ceb4196ad7f..86ff6c03a1e 100644 --- a/public/app/features/alerting/unified/triage/Workbench.tsx +++ b/public/app/features/alerting/unified/triage/Workbench.tsx @@ -23,7 +23,7 @@ import { Domain, Filter, WorkbenchRow } from './types'; type WorkbenchProps = { domain: Domain; data: WorkbenchRow[]; - groupBy?: string[]; // @TODO proper type + groupBy?: string[]; filterBy?: Filter[]; queryRunner: SceneQueryRunner; }; @@ -36,13 +36,30 @@ function renderWorkbenchRow( leftColumnWidth: number, domain: Domain, key: React.Key, + enableFolderMeta: boolean, depth = 0 ): React.ReactElement { if (row.type === 'alertRule') { - return ; + return ( + + ); } else { const children = row.rows.map((childRow, childIndex) => - renderWorkbenchRow(childRow, leftColumnWidth, domain, `${key}-${generateRowKey(childRow, childIndex)}`, depth + 1) + renderWorkbenchRow( + childRow, + leftColumnWidth, + domain, + `${key}-${generateRowKey(childRow, childIndex)}`, + enableFolderMeta, + depth + 1 + ) ); // Check if this is a grafana_folder group and use FolderGroupRow @@ -99,11 +116,14 @@ function renderWorkbenchRow( │ │ │ │ └─────────────────────────┘ └───────────────────────────────────┘ */ -export function Workbench({ domain, data, queryRunner }: WorkbenchProps) { +export function Workbench({ domain, data, queryRunner, groupBy }: WorkbenchProps) { const styles = useStyles2(getStyles); const isLoading = !queryRunner.isDataReadyToDisplay(); const [pageIndex, setPageIndex] = useState(1); + + // Calculate once: show folder metadata only if not grouping by grafana_folder + const enableFolderMeta = !groupBy?.includes('grafana_folder'); // splitter for template and payload editor const splitter = useSplitter({ direction: 'row', @@ -151,7 +171,7 @@ export function Workbench({ domain, data, queryRunner }: WorkbenchProps) { ) : ( dataSlice.map((row, index) => { const rowKey = generateRowKey(row, index); - return renderWorkbenchRow(row, leftColumnWidth, domain, rowKey); + return renderWorkbenchRow(row, leftColumnWidth, domain, rowKey, enableFolderMeta); }) )} {hasMore && setPageIndex((prevIndex) => prevIndex + 1)} />} diff --git a/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx b/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx index 539dc2c4ed1..965e9b5ce31 100644 --- a/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx @@ -16,9 +16,16 @@ interface AlertRuleRowProps { leftColumnWidth: number; rowKey: React.Key; depth?: number; + enableFolderMeta?: boolean; } -export const AlertRuleRow = ({ row, leftColumnWidth, rowKey, depth = 0 }: AlertRuleRowProps) => { +export const AlertRuleRow = ({ + row, + leftColumnWidth, + rowKey, + depth = 0, + enableFolderMeta = true, +}: AlertRuleRowProps) => { const { ruleUID, folder, title } = row.metadata; const [isDrawerOpen, setIsDrawerOpen] = useState(false); @@ -45,12 +52,14 @@ export const AlertRuleRow = ({ row, leftColumnWidth, rowKey, depth = 0 }: AlertR /> } metadata={ - - - - {folder} - - + enableFolderMeta ? ( + + + + {folder} + + + ) : undefined } content={} depth={depth} diff --git a/public/app/features/alerting/unified/triage/scene/Workbench.tsx b/public/app/features/alerting/unified/triage/scene/Workbench.tsx index f202a5cd0a0..d83908c2e91 100644 --- a/public/app/features/alerting/unified/triage/scene/Workbench.tsx +++ b/public/app/features/alerting/unified/triage/scene/Workbench.tsx @@ -34,7 +34,7 @@ export function WorkbenchRenderer() { const { data } = runner.useState(); const rows = data ? convertToWorkbenchRows(data, groupByKeys) : []; - return ; + return ; } type DataPoint = Record, string> & Record; From 44beedd09a50530a81c9afbea1eff190b6bfa6b9 Mon Sep 17 00:00:00 2001 From: Misi Date: Thu, 30 Oct 2025 16:50:40 +0100 Subject: [PATCH 57/72] IAM: Handle NULL external_uid, is_provisioned correctly for Teams (#113219) * Handle NULL external_uid correctly with MySQL * Add NULL handling to is_provisioned column --- pkg/registry/apis/iam/legacy/team.go | 21 ++++++++++---- pkg/tests/apis/iam/team_integration_test.go | 31 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/pkg/registry/apis/iam/legacy/team.go b/pkg/registry/apis/iam/legacy/team.go index 51e68506fbd..874dea3dff9 100644 --- a/pkg/registry/apis/iam/legacy/team.go +++ b/pkg/registry/apis/iam/legacy/team.go @@ -2,6 +2,7 @@ package legacy import ( "context" + "database/sql" "errors" "fmt" "time" @@ -129,18 +130,18 @@ func (s *legacySQLStore) ListTeams(ctx context.Context, ns claims.NamespaceInfo, return nil, fmt.Errorf("expected non zero orgID") } - sql, err := s.sql(ctx) + sqlConn, err := s.sql(ctx) if err != nil { return nil, err } - req := newListTeams(sql, &query) + req := newListTeams(sqlConn, &query) q, err := sqltemplate.Execute(sqlQueryTeamsTemplate, 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()...) + rows, err := sqlConn.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) defer func() { if rows != nil { _ = rows.Close() @@ -155,11 +156,21 @@ func (s *legacySQLStore) ListTeams(ctx context.Context, ns claims.NamespaceInfo, var lastID int64 for rows.Next() { t := team.Team{} - err = rows.Scan(&t.ID, &t.UID, &t.Name, &t.Email, &t.ExternalUID, &t.IsProvisioned, &t.Created, &t.Updated) + var externalUID sql.NullString + var isProvisioned sql.NullBool + err = rows.Scan(&t.ID, &t.UID, &t.Name, &t.Email, &externalUID, &isProvisioned, &t.Created, &t.Updated) if err != nil { return res, err } + if externalUID.Valid { + t.ExternalUID = externalUID.String + } + + if isProvisioned.Valid { + t.IsProvisioned = isProvisioned.Bool + } + lastID = t.ID res.Teams = append(res.Teams, t) if len(res.Teams) > int(query.Pagination.Limit)-1 { @@ -170,7 +181,7 @@ func (s *legacySQLStore) ListTeams(ctx context.Context, ns claims.NamespaceInfo, } if query.UID == "" { - res.RV, err = sql.GetResourceVersion(ctx, "team", "updated") + res.RV, err = sqlConn.GetResourceVersion(ctx, "team", "updated") } return res, err diff --git a/pkg/tests/apis/iam/team_integration_test.go b/pkg/tests/apis/iam/team_integration_test.go index e8bb115dfb7..16d27456e03 100644 --- a/pkg/tests/apis/iam/team_integration_test.go +++ b/pkg/tests/apis/iam/team_integration_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "testing" + "time" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/errors" @@ -222,6 +223,36 @@ func doTeamCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHelper) { require.Equal(t, createdUID, fetched.GetName()) require.Equal(t, "default", fetched.GetNamespace()) + + // Cleanup + err = teamClient.Resource.Delete(ctx, createdUID, metav1.DeleteOptions{}) + require.NoError(t, err) + }) + + t.Run("should list teams correctly", func(t *testing.T) { + ctx := context.Background() + + teamClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrTeams, + }) + + // For ensuring that it is able to list a team which has external_uid = null and is_provisioned = null + // only matters when legacy storage is involved + env := helper.GetEnv() + res, err := env.SQLStore.GetSqlxSession().Exec(ctx, "INSERT INTO team (org_id, uid, name, email, is_provisioned, external_uid, created, updated) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + helper.Org1.Admin.Identity.GetOrgID(), "t000000001", "List Team 1", "list-team-1@example.com", nil, nil, time.Now(), time.Now()) + require.NoError(t, err) + require.NotNil(t, res) + + list, err := teamClient.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.NotNil(t, list) + + // Cleanup + _, err = env.SQLStore.GetSqlxSession().Exec(ctx, "DELETE FROM team WHERE uid = ?", "t000000001") + require.NoError(t, err) }) } From 72e244c1e7117a03400429330ae1dac354a169c1 Mon Sep 17 00:00:00 2001 From: Jay Clifford <45856600+Jayclifford345@users.noreply.github.com> Date: Thu, 30 Oct 2025 15:57:16 +0000 Subject: [PATCH 58/72] fix(nav): Add tooltip to help button (#113225) Co-authored-by: Jack Baldry --- .../AppChrome/TopBar/HelpTopBarButton.tsx | 15 ++++++++++++++- public/locales/en-US/grafana.json | 7 ++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/public/app/core/components/AppChrome/TopBar/HelpTopBarButton.tsx b/public/app/core/components/AppChrome/TopBar/HelpTopBarButton.tsx index 62aed5080ef..02215cda4fd 100644 --- a/public/app/core/components/AppChrome/TopBar/HelpTopBarButton.tsx +++ b/public/app/core/components/AppChrome/TopBar/HelpTopBarButton.tsx @@ -34,7 +34,12 @@ export const HelpTopBarButton = memo(function HelpTopBarButton({ isSmallScreen } if (isSmallScreen || !enrichedHelpNode.hideFromTabs || interactiveLearningPluginId === undefined) { return ( } placement="bottom-end"> - + ); } @@ -48,6 +53,14 @@ export const HelpTopBarButton = memo(function HelpTopBarButton({ isSmallScreen } icon="question-circle" aria-label={t('navigation.help.aria-label', 'Help')} className={isOpen ? styles.helpButtonActive : undefined} + tooltip={ + isOpen + ? t( + 'navigation.help.interactive-learning.close-tooltip', + 'Close interactive learning, help, and documentation' + ) + : t('navigation.help.interactive-learning.open-tooltip', 'Open interactive learning, help, and documentation') + } onClick={() => { if (isOpen) { setDockedComponentId(undefined); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index fb177e68314..c1736af9a50 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -10597,7 +10597,12 @@ } }, "help": { - "aria-label": "Help" + "aria-label": "Help", + "interactive-learning": { + "close-tooltip": "Close interactive learning, help, and documentation", + "open-tooltip": "Open interactive learning, help, and documentation" + }, + "tooltip": "Get help and useful links" }, "invite-user": { "invite-button": "Invite", From 7fbe2e5962c84246f95ecce23a6bb9b1a8223c2c Mon Sep 17 00:00:00 2001 From: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:02:09 -0700 Subject: [PATCH 59/72] PanelTimeSettings: Update wording (#113176) * PanelTimeSettings: Update wording * Update wording --- .../panel-timerange/PanelTimeRangeDrawer.tsx | 18 ++++++++---------- public/locales/en-US/grafana.json | 16 ++++++++-------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRangeDrawer.tsx b/public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRangeDrawer.tsx index 215a5fba5d4..234b2570707 100644 --- a/public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRangeDrawer.tsx +++ b/public/app/features/dashboard-scene/scene/panel-timerange/PanelTimeRangeDrawer.tsx @@ -100,17 +100,17 @@ export class PanelTimeRangeDrawer extends SceneObjectBase @@ -129,7 +129,7 @@ export class PanelTimeRangeDrawer extends SceneObjectBase - - Time window comparison - + Time comparison @@ -172,10 +170,10 @@ export class PanelTimeRangeDrawer extends SceneObjectBase Date: Thu, 30 Oct 2025 09:12:04 -0700 Subject: [PATCH 60/72] Geomap: Move beta layers to GA (#113186) --- .../visualizations/geomap/index.md | 30 +++++-------------- .../panel/geomap/layers/data/networkLayer.tsx | 2 -- .../panel/geomap/layers/data/photosLayer.tsx | 2 -- .../panel/geomap/layers/data/routeLayer.tsx | 2 -- 4 files changed, 7 insertions(+), 29 deletions(-) diff --git a/docs/sources/visualizations/panels-visualizations/visualizations/geomap/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/geomap/index.md index 241622708db..fe941617bce 100644 --- a/docs/sources/visualizations/panels-visualizations/visualizations/geomap/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/geomap/index.md @@ -218,18 +218,14 @@ There are seven map layer types to choose from in a geomap. - [Heatmap](#heatmap-layer) visualizes a heatmap of the data. - [GeoJSON](#geojson-layer) renders static data from a GeoJSON file. - [Night / Day](#night--day-layer) renders a night / day region. -- [Route (Beta)](#route-layer-beta) render data points as a route. -- [Photos (Beta)](#photos-layer-beta) renders a photo at each data point. -- [Network (Beta)](#network-layer-beta) visualizes a network graph from the data. +- [Route](#route-layer) render data points as a route. +- [Photos](#photos-layer) renders a photo at each data point. +- [Network](#network-layer) visualizes a network graph from the data. - [Open Street Map](#open-street-map-layer) adds a map from a collaborative free geographic world database. - [CARTO basemap](#carto-basemap-layer) adds a layer from CARTO Raster basemaps. - [ArcGIS MapServer](#arcgis-mapserver-layer) adds a layer from an ESRI ArcGIS MapServer. - [XYZ Tile layer](#xyz-tile-layer) adds a map from a generic tile layer. -{{< admonition type="note" >}} -Beta is equivalent to the [public preview](/docs/release-life-cycle/) release stage. -{{< /admonition >}} - There are also two experimental (or alpha) layer types. - **Icon at last point (alpha)** renders an icon at the last data point. @@ -361,11 +357,7 @@ The Night / Day layer displays night and day regions based on the current time r [Extensions for OpenLayers - DayNight](https://viglino.github.io/ol-ext/examples/layer/map.daynight.html) -#### Route layer (Beta) - -{{< admonition type="caution" >}} -The Route layer is currently in [public preview](/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available. -{{< /admonition >}} +#### Route layer The Route layer renders data points as a route. @@ -390,11 +382,7 @@ The layer can also render a route with arrows. [Extensions for OpenLayers - Flow Line Style](http://viglino.github.io/ol-ext/examples/style/map.style.gpxline.html) -#### Photos layer (Beta) - -{{< admonition type="caution" >}} -The Photos layer is currently in [public preview](/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available. -{{< /admonition >}} +#### Photos layer The Photos layer renders a photo at each data point. @@ -417,11 +405,7 @@ The Photos layer renders a photo at each data point. [Extensions for OpenLayers - Image Photo Style](http://viglino.github.io/ol-ext/examples/style/map.style.photo.html) -#### Network layer (Beta) - -{{< admonition type="caution" >}} -The Network layer is currently in [public preview](/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available. -{{< /admonition >}} +#### Network layer The Network layer renders a network graph. This layer supports the same [data format supported by the node graph visualization](ref:data-format) with the addition of [geospatial data](#location-mode) included in the nodes data. The geospatial data is used to locate and render the nodes on the map. @@ -709,7 +693,7 @@ Displays debug information in the upper right corner. This can be useful for deb #### Tooltip -Tooltips are supported for the **Markers**, **Heatmap**, **Photos** (beta) layers. +Tooltips are supported for the **Markers**, **Heatmap**, **Photos** layers. For these layer types, choose from the following tooltip options: - **None** displays tooltips only when a data point is clicked. diff --git a/public/app/plugins/panel/geomap/layers/data/networkLayer.tsx b/public/app/plugins/panel/geomap/layers/data/networkLayer.tsx index e6375e79159..b4b70f77724 100644 --- a/public/app/plugins/panel/geomap/layers/data/networkLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/networkLayer.tsx @@ -18,7 +18,6 @@ import { EventBus, DataFrame, Field, - PluginState, } from '@grafana/data'; import { TextDimensionMode } from '@grafana/schema'; import { FrameVectorSource } from 'app/features/geo/utils/frameVectorSource'; @@ -69,7 +68,6 @@ export const networkLayer: MapLayerRegistryItem = { isBaseMap: false, showLocation: true, hideOpacity: true, - state: PluginState.beta, /** * Function that configures transformation and returns a transformer diff --git a/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx b/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx index 86932984cc2..c6e2ad7e402 100644 --- a/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/photosLayer.tsx @@ -9,7 +9,6 @@ import { PanelData, GrafanaTheme2, EventBus, - PluginState, FieldType, Field, MapLayerOptions, @@ -71,7 +70,6 @@ export const photosLayer: MapLayerRegistryItem = { isBaseMap: false, showLocation: true, hideOpacity: true, - state: PluginState.beta, /** * Function that configures transformation and returns a transformer diff --git a/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx b/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx index 16fd90076bd..ce220dd57f9 100644 --- a/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/routeLayer.tsx @@ -14,7 +14,6 @@ import { MapLayerRegistryItem, PanelData, GrafanaTheme2, - PluginState, EventBus, DataHoverEvent, DataHoverClearEvent, @@ -79,7 +78,6 @@ export const routeLayer: MapLayerRegistryItem = { description: 'Render data points as a route', isBaseMap: false, showLocation: true, - state: PluginState.beta, /** * Function that configures transformation and returns a transformer From 8d5e5e2eadb2365308f8c17f5804735b29146132 Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Thu, 30 Oct 2025 12:17:06 -0400 Subject: [PATCH 61/72] SaveDashboardAsForm: Adjust form field spacing (#113243) --- .../saving/SaveDashboardAsForm.tsx | 116 +++++++++--------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx index 896c2fa3474..4d90a871440 100644 --- a/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx +++ b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx @@ -147,64 +147,66 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) { return ( onSave(false))}> - } - invalid={!!errors.title} - error={errors.title?.message} - > - - - } - invalid={!!errors.description} - error={errors.description?.message} - > -